@rivus/agent 0.11.0 → 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 [{
75
- runId: event.runId,
76
- text: state.finalText,
77
- type: "update_text"
78
- }];
79
- case "agent_run_completed": 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 [{
80
68
  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,13 +594,13 @@ 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
@@ -640,7 +682,7 @@ async function readSnapshot$2(filePath) {
640
682
  const raw = await readPersistenceFile(filePath);
641
683
  if (raw === void 0) return [];
642
684
  const value = JSON.parse(raw);
643
- if (!isRecord$4(value) || value.version !== SNAPSHOT_VERSION || !Array.isArray(value.sessions)) throw new Error("Feishu session store must contain version 1 sessions");
685
+ if (!isRecord$5(value) || value.version !== SNAPSHOT_VERSION || !Array.isArray(value.sessions)) throw new Error("Feishu session store must contain version 1 sessions");
644
686
  return value.sessions.map(readRecord$2);
645
687
  }
646
688
  async function writeSnapshot$1(filePath, records) {
@@ -650,7 +692,7 @@ async function writeSnapshot$1(filePath, records) {
650
692
  });
651
693
  }
652
694
  function readRecord$2(value) {
653
- if (!isRecord$4(value) || typeof value.baseSessionKey !== "string" || !Number.isSafeInteger(value.generation)) throw new Error("Feishu session record must contain a baseSessionKey and generation");
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");
654
696
  return {
655
697
  baseSessionKey: value.baseSessionKey,
656
698
  generation: value.generation
@@ -712,6 +754,10 @@ function createSessionScheduler(options) {
712
754
  const runtime = runtimes.get(input.sessionKey);
713
755
  return runtime ? (await runtime).cancel?.(input) ?? false : false;
714
756
  },
757
+ steer: async (input) => {
758
+ const runtime = runtimes.get(input.sessionKey);
759
+ return runtime ? (await runtime).steer?.(input) ?? false : false;
760
+ },
715
761
  dispose: async () => {
716
762
  if (disposed) return;
717
763
  disposed = true;
@@ -1191,6 +1237,18 @@ function createAgentRunUpdateHandler(handle) {
1191
1237
  catch: (cause) => cause
1192
1238
  });
1193
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
+ }
1194
1252
  function createAgentHarness(options) {
1195
1253
  if (options.runTimeoutMs !== void 0 && (!Number.isSafeInteger(options.runTimeoutMs) || options.runTimeoutMs < 1)) throw new Error("Agent run timeout must be a positive integer");
1196
1254
  let activeRun;
@@ -1254,7 +1312,8 @@ function createAgentHarness(options) {
1254
1312
  const previousSessionTranscript = getSessionTranscript(command.sessionKey);
1255
1313
  const cancellation = {
1256
1314
  abortController: new AbortController(),
1257
- deferred: yield* Deferred.make()
1315
+ deferred: yield* Deferred.make(),
1316
+ steering: createSteeringChannel()
1258
1317
  };
1259
1318
  activeCancellation = cancellation;
1260
1319
  const events = [];
@@ -1331,6 +1390,7 @@ function createAgentHarness(options) {
1331
1390
  ...previousSessionTranscript.turnCount > 0 ? { previousSessionTranscript } : {},
1332
1391
  runId,
1333
1392
  sessionKey: command.sessionKey,
1393
+ ...options.loop.supportsSteering ? { steering: cancellation.steering } : {},
1334
1394
  text: command.text
1335
1395
  };
1336
1396
  const loop = Effect.try({
@@ -1430,10 +1490,20 @@ function createAgentHarness(options) {
1430
1490
  }
1431
1491
  return completed;
1432
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
+ });
1433
1499
  const requestSessionCancellation = (sessionKey, runId, reason) => {
1434
1500
  if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
1435
1501
  return requestCancellation(runId, reason);
1436
1502
  };
1503
+ const requestSessionSteering = (sessionKey, runId, text) => {
1504
+ if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
1505
+ return requestSteering(runId, text);
1506
+ };
1437
1507
  const requestSessionActiveCancellation = (sessionKey, reason) => {
1438
1508
  if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
1439
1509
  return requestCancellation(activeRun.runId, reason);
@@ -1531,9 +1601,11 @@ function createAgentHarness(options) {
1531
1601
  return yield* requestCancellation(activeRun.runId, reason);
1532
1602
  }),
1533
1603
  cancelRun: requestCancellation,
1604
+ steerRun: requestSteering,
1534
1605
  forSession: (sessionKey) => ({
1535
1606
  cancelActiveRun: (reason) => requestSessionActiveCancellation(sessionKey, reason),
1536
1607
  cancelRun: (runId, reason) => requestSessionCancellation(sessionKey, runId, reason),
1608
+ steerRun: (runId, text) => requestSessionSteering(sessionKey, runId, text),
1537
1609
  getActiveRun: () => getSessionActiveRun(sessionKey),
1538
1610
  getActiveRunState: () => getSessionActiveRunState(sessionKey),
1539
1611
  getAvailability: () => getSessionAvailability(sessionKey),
@@ -1961,7 +2033,7 @@ function createAgentCommandFromFeishuCardAction(payload) {
1961
2033
  const messageId = readNonEmptyString(event.context?.open_message_id);
1962
2034
  if (!messageId) return yield* Effect.fail(new InvalidFeishuCardAction("card action message id is missing"));
1963
2035
  const value = event.action?.value;
1964
- 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"));
1965
2037
  if (value.rivus_action === "resolve_interaction") {
1966
2038
  const interactionId = readNonEmptyString(value.interaction_id);
1967
2039
  if (!interactionId) return yield* Effect.fail(new InvalidFeishuCardAction("card interaction action requires an interaction id"));
@@ -2006,7 +2078,7 @@ function readInteractionAction(value) {
2006
2078
  function readNonEmptyString(value) {
2007
2079
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
2008
2080
  }
2009
- function isRecord$3(value) {
2081
+ function isRecord$4(value) {
2010
2082
  return typeof value === "object" && value !== null && !Array.isArray(value);
2011
2083
  }
2012
2084
  //#endregion
@@ -2043,10 +2115,7 @@ function encodeSegment(value) {
2043
2115
  return encodeURIComponent(value);
2044
2116
  }
2045
2117
  //#endregion
2046
- //#region src/application/feishu/feishu-message-intake.ts
2047
- function readTenantKey(payload) {
2048
- return payload.event.sender?.tenant_key ?? payload.header?.tenant_key ?? "";
2049
- }
2118
+ //#region src/application/feishu/feishu-message-content.ts
2050
2119
  var UnsupportedFeishuMessage = class extends Error {
2051
2120
  messageType;
2052
2121
  name = "UnsupportedFeishuMessage";
@@ -2065,10 +2134,76 @@ var InvalidFeishuMessageContent = class extends Error {
2065
2134
  this.reason = reason;
2066
2135
  }
2067
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
+ }
2068
2203
  function createAgentCommandFromFeishuMessage(payload, options) {
2069
2204
  return Effect.gen(function* () {
2070
2205
  const message = payload.event.message;
2071
- 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);
2206
+ const normalizedText = yield* readNormalizedMessageText(payload, options);
2072
2207
  const sessionReference = toSessionReference(payload, options);
2073
2208
  const baseSessionKey = yield* createFeishuSessionKey(sessionReference);
2074
2209
  const reset = parseNewSessionCommand(message.message_id, normalizedText);
@@ -2108,9 +2243,19 @@ function createAgentCommandFromFeishuMessage(payload, options) {
2108
2243
  };
2109
2244
  });
2110
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
+ }
2111
2255
  function normalizeTrustedBotMention(text, mentions, botOpenId) {
2112
2256
  if (!botOpenId || !mentions) return text.trim();
2113
- 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();
2114
2259
  }
2115
2260
  function validateSkillCommand(text) {
2116
2261
  if (!/^\/skill(?::|\s|$)/.test(text)) return Effect.void;
@@ -2139,7 +2284,7 @@ function describeFeishuMessageIntake(payload, options) {
2139
2284
  commandType: "cancel_run",
2140
2285
  messageId: command.messageId,
2141
2286
  reason: command.reason,
2142
- runId: command.runId,
2287
+ ...command.runId ? { runId: command.runId } : {},
2143
2288
  sessionKey: command.sessionKey ?? (yield* createFeishuSessionKey(sessionReference)),
2144
2289
  sessionReference
2145
2290
  };
@@ -2167,67 +2312,94 @@ function toSessionReference(payload, options) {
2167
2312
  ...payload.event.message.thread_id ? { threadId: payload.event.message.thread_id } : {}
2168
2313
  };
2169
2314
  }
2170
- function parseTextContent(content) {
2171
- return Effect.try({
2172
- try: () => {
2173
- const parsed = JSON.parse(content);
2174
- if (typeof parsed.text !== "string") throw new Error("text content is missing");
2175
- return parsed.text;
2176
- },
2177
- catch: (error) => new InvalidFeishuMessageContent(error instanceof Error ? error.message : "invalid JSON content")
2178
- });
2179
- }
2180
- function parsePostContent(content) {
2181
- return Effect.try({
2182
- try: () => {
2183
- const parsed = JSON.parse(content);
2184
- const paragraphs = Array.isArray(parsed.content_v2) ? parsed.content_v2 : parsed.content;
2185
- if (!Array.isArray(paragraphs)) throw new Error("post content is missing");
2186
- const body = paragraphs.map((paragraph) => {
2187
- if (!Array.isArray(paragraph)) throw new Error("post paragraph is invalid");
2188
- return paragraph.map((element) => {
2189
- if (element === null || typeof element !== "object") return "";
2190
- if ("text" in element && typeof element.text === "string") return element.text;
2191
- if ("user_name" in element && typeof element.user_name === "string") return `@${element.user_name}`;
2192
- return "";
2193
- }).join("");
2194
- }).join("\n").trim();
2195
- const text = [typeof parsed.title === "string" ? parsed.title.trim() : "", body].filter((part) => part.length > 0).join("\n");
2196
- if (!text) throw new Error("post content has no text");
2197
- return text;
2198
- },
2199
- catch: (error) => new InvalidFeishuMessageContent(error instanceof Error ? error.message : "invalid JSON content")
2200
- });
2201
- }
2202
2315
  function parseCancelRunCommand(messageId, text) {
2203
2316
  const trimmed = text.trim();
2204
2317
  if (!/^\/cancel(?:\s|$)/.test(trimmed)) return Effect.succeed(void 0);
2205
- const match = /^\/cancel\s+(\S+)\s*$/.exec(trimmed);
2206
- 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"));
2207
2320
  return Effect.succeed({
2208
2321
  messageId,
2209
2322
  reason: "Feishu cancel command",
2210
- runId: match[1],
2323
+ ...match[1] ? { runId: match[1] } : {},
2211
2324
  type: "cancel_run"
2212
2325
  });
2213
2326
  }
2214
2327
  //#endregion
2215
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 = "当前没有正在运行的任务。";
2216
2332
  function createFeishuAgentDaemon(options) {
2217
2333
  const sessionStore = options.sessionStore ?? createFeishuSessionStore();
2218
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
+ }),
2219
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))),
2220
2346
  promptWithUpdates: (command, onUpdate) => options.harness.promptWithUpdates(command, onUpdate)
2221
2347
  };
2222
2348
  const seenCardActionTokens = /* @__PURE__ */ new Set();
2223
2349
  const seenMessageIds = /* @__PURE__ */ new Set();
2224
2350
  const cancelRun = (command) => Effect.gen(function* () {
2225
- 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;
2226
2360
  return {
2227
2361
  cancelled,
2228
2362
  messageId: command.messageId,
2229
2363
  ...cancelled ? {} : { reason: "not_active" },
2230
- 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
2231
2403
  };
2232
2404
  });
2233
2405
  return {
@@ -2242,6 +2414,43 @@ function createFeishuAgentDaemon(options) {
2242
2414
  if (inbound.type === "cancel_run" && inbound.token) seenCardActionTokens.add(inbound.token);
2243
2415
  return result;
2244
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
+ }),
2245
2454
  handleMessage: (payload, handleOptions) => {
2246
2455
  let dedupeMessageId;
2247
2456
  const sideEffectsDisabled = handleOptions?.sideEffects === "disabled";
@@ -2255,29 +2464,29 @@ function createFeishuAgentDaemon(options) {
2255
2464
  ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
2256
2465
  sessionStore
2257
2466
  });
2258
- if (options.dedupe && seenMessageIds.has(inbound.messageId)) return {
2259
- messageId: inbound.messageId,
2260
- reason: "duplicate",
2261
- skipped: true
2262
- };
2467
+ const duplicate = duplicateResult(inbound.messageId);
2468
+ if (duplicate) return duplicate;
2263
2469
  if (options.dedupe) {
2264
2470
  seenMessageIds.add(inbound.messageId);
2265
2471
  dedupeMessageId = inbound.messageId;
2266
2472
  }
2267
- if (inbound.type === "cancel_run") return yield* cancelRun(inbound);
2268
- if (inbound.type === "new_session") {
2269
- if (!sideEffectsDisabled && options.reply) yield* options.reply(inbound.messageId, "已开启新会话。请发送下一条指令。");
2270
- return {
2271
- messageId: inbound.messageId,
2272
- previousSessionKey: inbound.previousSessionKey,
2273
- reset: true,
2274
- sessionKey: inbound.sessionKey
2275
- };
2473
+ if (inbound.type === "cancel_run") {
2474
+ const result = yield* cancelRun(inbound);
2475
+ if (!sideEffectsDisabled) yield* replyToCancellation(inbound.messageId, result.cancelled);
2476
+ return result;
2276
2477
  }
2478
+ if (inbound.type === "new_session") return yield* resetSession(inbound, !sideEffectsDisabled);
2479
+ const steered = yield* steerPrompt(inbound, !sideEffectsDisabled);
2480
+ if (steered) return steered;
2277
2481
  const projector = createFeishuStreamProjector();
2278
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;
2279
2487
  const command = {
2280
2488
  ...inbound.command,
2489
+ text: promptText,
2281
2490
  ...options.resolveInvocation ? { invocation: options.resolveInvocation(payload, inbound.conversationId) } : {}
2282
2491
  };
2283
2492
  const result = yield* execution.promptWithUpdates(command, (update) => Effect.gen(function* () {
@@ -2350,6 +2559,32 @@ function createFeishuMessageQueue(options) {
2350
2559
  pending: repository.pendingCount(),
2351
2560
  reason: admission
2352
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
+ }
2353
2588
  return {
2354
2589
  accepted: true,
2355
2590
  messageId,
@@ -2368,6 +2603,22 @@ function createFeishuMessageQueue(options) {
2368
2603
  handled: false,
2369
2604
  reason: "empty"
2370
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
+ }
2371
2622
  const result = yield* options.handleMessage(message.payload, message.options).pipe(Effect.catchAll((error) => repository.fail({
2372
2623
  availableAt: new Date(Date.parse(now()) + retryDelayMs).toISOString(),
2373
2624
  failedAt: now(),
@@ -2790,7 +3041,10 @@ function toast(type, enUs, zhCn) {
2790
3041
  //#endregion
2791
3042
  //#region src/infrastructure/feishu/feishu-event-handlers.ts
2792
3043
  function createFeishuEventHandlers(options) {
2793
- 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
+ } };
2794
3048
  if (options.cardActions) return {
2795
3049
  ...handlers,
2796
3050
  "card.action.trigger": (payload) => Effect.runPromise(options.cardActions.handleCardAction(payload).pipe(Effect.map(createFeishuCardActionCallbackResponse), Effect.catchAll(() => Effect.succeed(createFeishuCardActionErrorResponse()))))
@@ -2821,23 +3075,24 @@ function createFeishuAgentRuntime(options) {
2821
3075
  runIds: options.runIds
2822
3076
  });
2823
3077
  const daemon = createFeishuAgentDaemon({
3078
+ ...options.acknowledgeSteering ? { acknowledgeSteering: options.acknowledgeSteering } : {},
2824
3079
  agentId: options.agentId,
2825
3080
  ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3081
+ dedupe: true,
2826
3082
  ...options.finalizeRun ? { finalizeRun: options.finalizeRun } : {},
2827
3083
  harness,
2828
3084
  ...options.prepareRun ? { prepareRun: options.prepareRun } : {},
3085
+ ...options.promptContext ? { promptContext: options.promptContext } : {},
2829
3086
  publish: options.publish,
3087
+ ...options.reply ? { reply: options.reply } : {},
2830
3088
  sessionStore
2831
3089
  });
2832
3090
  let lastAccepted;
2833
3091
  let lastHandled;
2834
3092
  const queue = createFeishuMessageQueue({
3093
+ handleControl: (payload) => daemon.trySteerMessage(payload),
2835
3094
  handleMessage: (payload, handleOptions) => {
2836
- const effect = daemon.handleMessage(payload, handleOptions).pipe(Effect.tap((result) => describeFeishuMessageIntake(payload, {
2837
- agentId: options.agentId,
2838
- ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
2839
- sessionStore
2840
- }).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* () {
2841
3096
  const observedAt = yield* options.clock.now;
2842
3097
  lastHandled = {
2843
3098
  intake,
@@ -2849,6 +3104,11 @@ function createFeishuAgentRuntime(options) {
2849
3104
  return options.periodicFlush ? options.periodicFlush.withPeriodicFlush(effect) : effect;
2850
3105
  },
2851
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
+ }) } : {},
2852
3112
  shouldRetryError: isRetryableFeishuMessageError
2853
3113
  });
2854
3114
  const worker = createFeishuMessageWorker({ queue });
@@ -2865,7 +3125,10 @@ function createFeishuAgentRuntime(options) {
2865
3125
  drainOne: () => queue.drainOne(),
2866
3126
  handlers: createFeishuEventHandlers({
2867
3127
  cardActions: daemon,
2868
- queue: observedQueue
3128
+ queue: {
3129
+ ...observedQueue,
3130
+ pending: () => queue.pending()
3131
+ }
2869
3132
  }),
2870
3133
  harness,
2871
3134
  pending: () => queue.pending(),
@@ -2875,21 +3138,75 @@ function createFeishuAgentRuntime(options) {
2875
3138
  ...lastHandled ? { lastHandled } : {}
2876
3139
  }),
2877
3140
  replayReceiveMessage: (payload, replayOptions) => Effect.gen(function* () {
2878
- const intake = yield* describeFeishuMessageIntake(payload, {
3141
+ const intakeOptions = {
2879
3142
  agentId: options.agentId,
2880
- ...options.botOpenId ? { botOpenId: options.botOpenId } : {}
2881
- });
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;
2882
3169
  return {
2883
- accepted: yield* observedQueue.accept(payload, replayOptions),
2884
- drained: yield* worker.drainAvailable(),
3170
+ accepted,
3171
+ drained,
2885
3172
  intake
2886
3173
  };
2887
3174
  }),
2888
3175
  worker
2889
3176
  };
2890
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
+ }
2891
3208
  function isRetryableFeishuMessageError(error) {
2892
- 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);
2893
3210
  }
2894
3211
  function summarizeReceiveMessage(payload) {
2895
3212
  const tenantKey = readTenantKey(payload);
@@ -2900,21 +3217,47 @@ function summarizeReceiveMessage(payload) {
2900
3217
  ...payload.event.message.thread_id ? { threadId: payload.event.message.thread_id } : {}
2901
3218
  };
2902
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
+ }
2903
3255
  //#endregion
2904
3256
  //#region src/application/agent/agent-history.ts
2905
3257
  function restoreAgentHistory(eventLog) {
2906
3258
  return eventLog.readAll().pipe(Effect.map(replayAgentHistory));
2907
3259
  }
2908
3260
  //#endregion
2909
- //#region src/infrastructure/feishu/feishu-periodic-flush.ts
2910
- function createFeishuPeriodicFlush(options) {
2911
- const loop = Effect.forever(Effect.gen(function* () {
2912
- yield* options.sleep(options.intervalMs);
2913
- yield* options.flush();
2914
- }));
2915
- return { withPeriodicFlush: (effect) => Effect.raceFirst(effect, loop) };
2916
- }
2917
- //#endregion
2918
3261
  //#region src/infrastructure/feishu/feishu-websocket-daemon.ts
2919
3262
  function createLazyFeishuWebSocketEventDispatcher(create) {
2920
3263
  let dispatcher;
@@ -2988,7 +3331,7 @@ function createFeishuAgentRunCard(input) {
2988
3331
  content: `_${presentation.note}_`,
2989
3332
  tag: "markdown"
2990
3333
  },
2991
- ...input.status === "running" ? [{
3334
+ ...input.status === "running" && input.cancelButton !== false ? [{
2992
3335
  behaviors: [{
2993
3336
  type: "callback",
2994
3337
  value: {
@@ -3023,15 +3366,15 @@ function resolveRunPresentation(input) {
3023
3366
  switch (input.status) {
3024
3367
  case "running": return {
3025
3368
  content: input.text || "正在思考…",
3026
- note: input.generation ? "接续上一条消息,回答将实时更新" : "回答将实时更新",
3369
+ note: input.cancelButton === false ? `回答将实时更新;如需停止,请发送 \`/cancel ${input.runId}\`` : input.generation ? "接续上一条消息,回答将实时更新" : "回答将实时更新",
3027
3370
  template: "blue",
3028
3371
  title: input.generation ? "正在处理(续)" : "正在处理"
3029
3372
  };
3030
3373
  case "handoff": return {
3031
- content: "任务仍在后台处理,进度将继续显示在下一条消息。",
3032
- note: "本卡片已停止更新;后续进度见下一条消息",
3374
+ content: "这是同一任务的续卡。任务仍在后台处理,进度将继续显示在下一条消息。",
3375
+ note: "本卡片已停止更新;请查看下一条消息,不会创建第二个任务",
3033
3376
  template: "blue",
3034
- title: "已转到新消息"
3377
+ title: "同一任务已续卡"
3035
3378
  };
3036
3379
  case "completed": return {
3037
3380
  content: input.text || "已完成。",
@@ -3339,6 +3682,7 @@ function createFeishuCardTargetPreparation(options) {
3339
3682
  function createConfiguredFeishuCardKitTargetCreator(options) {
3340
3683
  return createFeishuCardKitOpenApiTargetCreator({
3341
3684
  baseUrl: options.config.feishu.baseUrl,
3685
+ ...options.cancelButton === void 0 ? {} : { cancelButton: options.cancelButton },
3342
3686
  client: options.client,
3343
3687
  elementId: options.elementId ?? DEFAULT_ELEMENT_ID,
3344
3688
  ...options.initialContent === void 0 ? {} : { initialContent: options.initialContent },
@@ -3359,6 +3703,7 @@ function createFeishuCardKitOpenApiTargetCreator(options) {
3359
3703
  runId: run.runId,
3360
3704
  sessionKey: run.sessionKey,
3361
3705
  status: "running",
3706
+ ...options.cancelButton === void 0 ? {} : { cancelButton: options.cancelButton },
3362
3707
  text: createOptions?.initialContent ?? initialContent
3363
3708
  })),
3364
3709
  type: "card_json"
@@ -3385,10 +3730,10 @@ function createFeishuCardKitOpenApiTargetCreator(options) {
3385
3730
  }
3386
3731
  function readStringData(response, field) {
3387
3732
  const data = response.body.data;
3388
- 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}`));
3389
3734
  return Effect.succeed(data[field]);
3390
3735
  }
3391
- function isRecord$2(value) {
3736
+ function isRecord$3(value) {
3392
3737
  return typeof value === "object" && value !== null && !Array.isArray(value);
3393
3738
  }
3394
3739
  //#endregion
@@ -3408,6 +3753,7 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3408
3753
  sleep: options.sleep
3409
3754
  });
3410
3755
  const creator = createConfiguredFeishuCardKitTargetCreator({
3756
+ ...options.cancelButton === void 0 ? {} : { cancelButton: options.cancelButton },
3411
3757
  client: options.client,
3412
3758
  config: options.config,
3413
3759
  ...options.elementId === void 0 ? {} : { elementId: options.elementId },
@@ -3433,6 +3779,12 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3433
3779
  rollover: cardRollover,
3434
3780
  sleep: options.sleep
3435
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
+ });
3436
3788
  return {
3437
3789
  prepareRun: createFeishuCardTargetPreparation({
3438
3790
  createTarget: (run) => creator.createTarget(run),
@@ -3441,15 +3793,20 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3441
3793
  rollover: cardRollover,
3442
3794
  supervisor,
3443
3795
  transport: {
3444
- running: () => supervisor.running(),
3796
+ running: () => supervisor.running() && streaming.running(),
3445
3797
  start: async () => {
3446
3798
  await Effect.runPromise(supervisor.recover().pipe(Effect.catchAll((error) => {
3447
3799
  const reported = options.onError?.(error);
3448
3800
  return reported instanceof Promise ? Effect.promise(() => reported) : Effect.void;
3449
3801
  })));
3450
3802
  supervisor.start();
3803
+ streaming.start();
3451
3804
  },
3452
- 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
+ }
3453
3810
  }
3454
3811
  };
3455
3812
  }
@@ -3457,9 +3814,74 @@ function resolveSupervisorInterval(leaseMs) {
3457
3814
  return Math.max(MIN_SUPERVISOR_INTERVAL_MS, Math.min(MAX_SUPERVISOR_INTERVAL_MS, Math.floor(leaseMs / 10)));
3458
3815
  }
3459
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
3460
3883
  //#region src/composition/rivus-daemon-bootstrap.ts
3461
3884
  const DEFAULT_WORKER_INTERVAL_MS$1 = 250;
3462
- const DEFAULT_RUN_TIMEOUT_MS = 900 * 1e3;
3463
3885
  function restoreConfiguredRivusDaemonBootstrap(options) {
3464
3886
  return options.eventLog.readAll().pipe(Effect.map((events) => createConfiguredRivusDaemonBootstrap({
3465
3887
  ...options,
@@ -3473,19 +3895,33 @@ function createConfiguredRivusDaemonBootstrap(options) {
3473
3895
  });
3474
3896
  const cardRollover = createConfiguredFeishuCardRolloverRuntime({
3475
3897
  cardTargets: options.cardTargets,
3898
+ cancelButton: false,
3476
3899
  client: openApiClient,
3477
3900
  clock: options.clock,
3478
3901
  config: options.config,
3479
3902
  ...options.cardLedger ? { ledger: options.cardLedger } : {},
3903
+ ...options.flushIntervalMs === void 0 ? {} : { flushIntervalMs: options.flushIntervalMs },
3480
3904
  ...options.observeCardRollover ? { observe: options.observeCardRollover } : {},
3481
3905
  ...options.onWorkerError ? { onError: options.onWorkerError } : {},
3482
3906
  sleep: options.sleep,
3483
3907
  ...options.cardRolloverIntervalMs === void 0 ? {} : { supervisorIntervalMs: options.cardRolloverIntervalMs }
3484
3908
  });
3485
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
+ });
3486
3922
  const prepareRun = (run) => cardRollover.prepareRun(run);
3487
- const periodicFlush = createPeriodicFlush(options, publisher);
3488
3923
  const runtime = createFeishuAgentRuntime({
3924
+ acknowledgeSteering: (messageId) => reactions.add(messageId),
3489
3925
  agentId: options.config.agentId,
3490
3926
  ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3491
3927
  clock: options.clock,
@@ -3495,10 +3931,11 @@ function createConfiguredRivusDaemonBootstrap(options) {
3495
3931
  ...options.initialEvents ? { initialEvents: options.initialEvents } : {},
3496
3932
  ...options.initialRunStates ? { initialRunStates: options.initialRunStates } : {},
3497
3933
  loop: options.loop,
3498
- periodicFlush,
3499
3934
  prepareRun,
3935
+ promptContext,
3500
3936
  publish: (action) => publisher.publish(action),
3501
- runTimeoutMs: options.runTimeoutMs ?? DEFAULT_RUN_TIMEOUT_MS,
3937
+ reply: (messageId, text) => replies.reply(messageId, text),
3938
+ ...options.runTimeoutMs === void 0 ? {} : { runTimeoutMs: options.runTimeoutMs },
3502
3939
  runIds: options.runIds
3503
3940
  });
3504
3941
  const websocketTransport = createFeishuWebSocketDaemon({
@@ -3553,13 +3990,6 @@ function createConfiguredRivusDaemonBootstrap(options) {
3553
3990
  workerLoop
3554
3991
  };
3555
3992
  }
3556
- function createPeriodicFlush(options, publisher) {
3557
- return createFeishuPeriodicFlush({
3558
- flush: () => publisher.flush(),
3559
- intervalMs: options.flushIntervalMs ?? options.config.feishu.streamMinIntervalMs,
3560
- sleep: options.sleep
3561
- });
3562
- }
3563
3993
  //#endregion
3564
3994
  //#region src/infrastructure/persistence/jsonl-agent-event-log.ts
3565
3995
  var AgentEventLogStoreError = class {
@@ -3596,7 +4026,7 @@ function parseJsonlEvents(raw) {
3596
4026
  return raw.split("\n").filter((line) => line.trim().length > 0).map((line, index) => parseAgentDomainEvent(JSON.parse(line), index + 1));
3597
4027
  }
3598
4028
  function parseAgentDomainEvent(value, lineNumber) {
3599
- 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}.`);
3600
4030
  const occurredAt = readDate(value, "occurredAt", lineNumber);
3601
4031
  switch (value.type) {
3602
4032
  case "agent_run_accepted": return {
@@ -3767,7 +4197,7 @@ function readOptionalNumber(value, field, lineNumber) {
3767
4197
  }
3768
4198
  function readRecord$1(value, field, lineNumber) {
3769
4199
  const fieldValue = value[field];
3770
- 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}.`);
3771
4201
  return fieldValue;
3772
4202
  }
3773
4203
  function readOptionalString(value, field, lineNumber) {
@@ -4775,13 +5205,13 @@ async function saveChains(filePath, chains) {
4775
5205
  }
4776
5206
  }
4777
5207
  function parseChains(value) {
4778
- 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.");
4779
5209
  if (value.version === void 0) return Object.entries(value).map(([runId, target]) => parseLegacyChain(runId, target));
4780
- 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)}`);
4781
5211
  return Object.entries(value.runs).map(([runId, chain]) => parseChain(runId, chain));
4782
5212
  }
4783
5213
  function parseLegacyChain(runId, target) {
4784
- 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}'.`);
4785
5215
  return createCardPresentationChain({
4786
5216
  cardId: target.cardId,
4787
5217
  createdAt: LEGACY_PRESENTATION_TIMESTAMP,
@@ -4793,9 +5223,9 @@ function parseLegacyChain(runId, target) {
4793
5223
  });
4794
5224
  }
4795
5225
  function parseChain(runId, value) {
4796
- 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}'.`);
4797
5227
  const chain = value;
4798
- 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}'.`);
4799
5229
  activeCardPresentation(chain);
4800
5230
  return chain;
4801
5231
  }
@@ -5167,6 +5597,15 @@ function readRedactedString(serialized) {
5167
5597
  return typeof value === "string" ? value : serialized;
5168
5598
  }
5169
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
5170
5609
  //#region src/testing/fakes.ts
5171
5610
  function createFixedClock(now) {
5172
5611
  return { now: Effect.succeed(now) };
@@ -5596,7 +6035,7 @@ async function load$1(filePath) {
5596
6035
  for (const [index, line] of raw.split("\n").entries()) {
5597
6036
  if (!line.trim()) continue;
5598
6037
  const envelope = JSON.parse(line);
5599
- 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}`);
5600
6039
  const record = envelope.record;
5601
6040
  const previous = latest.get(record.operationId);
5602
6041
  if (record.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid tool operation revision at line ${index + 1}`);
@@ -5619,7 +6058,7 @@ function isValidTransition$1(previous, next) {
5619
6058
  }
5620
6059
  }
5621
6060
  function isOperationRecord(value) {
5622
- 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;
5623
6062
  const binding = value.binding;
5624
6063
  const state = value.state;
5625
6064
  if (typeof value.operationId !== "string" || !Number.isInteger(value.revision) || value.revision < 1) return false;
@@ -5650,7 +6089,7 @@ function isStableJson(value) {
5650
6089
  }
5651
6090
  }
5652
6091
  function isReconciliation(value) {
5653
- 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");
5654
6093
  }
5655
6094
  //#endregion
5656
6095
  //#region src/infrastructure/persistence/jsonl-recovery-control.ts
@@ -5687,14 +6126,20 @@ function isMissingDirectory(error) {
5687
6126
  function createAgentHarnessPooledRuntime(harness) {
5688
6127
  return {
5689
6128
  cancel: (input) => Effect.runPromise(harness.forSession(input.sessionKey).cancelRun(input.runId, input.reason)),
5690
- run: (input) => Effect.runPromise(harness.promptWithUpdates({
5691
- ...input.invocation ? { invocation: input.invocation } : {},
5692
- sessionKey: input.sessionKey,
5693
- text: input.text
5694
- }, (update) => Effect.tryPromise({
5695
- try: async () => input.onUpdate?.(update),
5696
- catch: (error) => error
5697
- })))
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
+ }
5698
6143
  };
5699
6144
  }
5700
6145
  //#endregion
@@ -5707,12 +6152,15 @@ function createFeishuDeploymentEndpoint(options) {
5707
6152
  shutdownTimeoutMs: options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS
5708
6153
  });
5709
6154
  const daemon = createFeishuAgentDaemon({
6155
+ ...options.acknowledgeSteering ? { acknowledgeSteering: options.acknowledgeSteering } : {},
5710
6156
  agentId: options.agentId,
5711
6157
  botOpenId: options.botOpenId,
6158
+ dedupe: true,
5712
6159
  execution: execution.port,
5713
6160
  ...options.finalizeRun ? { finalizeRun: options.finalizeRun } : {},
5714
6161
  ...options.interactions ? { interactions: options.interactions } : {},
5715
6162
  ...options.prepareRun ? { prepareRun: options.prepareRun } : {},
6163
+ ...options.promptContext ? { promptContext: options.promptContext } : {},
5716
6164
  publish: options.publish,
5717
6165
  ...options.publishRunUpdate ? { publishRunUpdate: options.publishRunUpdate } : {},
5718
6166
  ...options.reply ? { reply: options.reply } : {},
@@ -5720,9 +6168,15 @@ function createFeishuDeploymentEndpoint(options) {
5720
6168
  ...options.endpointId ? { resolveInvocation: (payload, conversationId) => createInvocation(payload, options.endpointId, options.memoryTenantId, conversationId, options.projectSpaceId) } : {}
5721
6169
  });
5722
6170
  const queue = createFeishuMessageQueue({
6171
+ handleControl: (payload) => daemon.trySteerMessage(payload),
5723
6172
  handleMessage: (payload, handleOptions) => daemon.handleMessage(payload, handleOptions),
5724
6173
  ...options.inboxRepository ? { repository: options.inboxRepository } : {},
5725
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) } : {},
5726
6180
  shouldRetryError: isRetryableFeishuMessageError
5727
6181
  });
5728
6182
  const worker = createFeishuMessageWorker({
@@ -5740,14 +6194,17 @@ function createFeishuDeploymentEndpoint(options) {
5740
6194
  eventDispatcher: options.eventDispatcher,
5741
6195
  handlers: createFeishuEventHandlers({
5742
6196
  cardActions: daemon,
5743
- 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(() => {
5744
- Effect.runPromise(options.onCapacityExceeded(payload)).catch((error) => options.onWorkerError?.(error)).catch(() => void 0);
5745
- }))) : Effect.succeed({
5746
- accepted: false,
5747
- messageId: payload.event.message.message_id,
5748
- pending: queue.pending(),
5749
- reason: "ignored"
5750
- }) }
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
+ }
5751
6208
  })
5752
6209
  });
5753
6210
  return {
@@ -5788,6 +6245,10 @@ function createDeploymentExecution(options) {
5788
6245
  const activeHandles = /* @__PURE__ */ new Set();
5789
6246
  const activeRuns = /* @__PURE__ */ new Map();
5790
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
+ };
5791
6252
  const requestShutdownCancellation = (runId, sessionKey) => {
5792
6253
  const current = cancellationRequests.get(runId);
5793
6254
  if (current) return current;
@@ -5832,6 +6293,19 @@ function createDeploymentExecution(options) {
5832
6293
  if (errors.length > 1) throw new AggregateError(errors, "Active Agent run cancellation failed");
5833
6294
  },
5834
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
+ }),
5835
6309
  cancelRun: (input) => input.sessionKey === void 0 ? Effect.succeed(false) : Effect.tryPromise({
5836
6310
  try: () => options.cancel({
5837
6311
  reason: input.reason,
@@ -5840,6 +6314,23 @@ function createDeploymentExecution(options) {
5840
6314
  }),
5841
6315
  catch: (error) => error
5842
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
+ }),
5843
6334
  promptWithUpdates: (command, onUpdate) => Effect.tryPromise({
5844
6335
  try: async () => {
5845
6336
  const sessionKey = namespaceSessionKey(options.sessionNamespace, command.sessionKey);
@@ -7255,8 +7746,8 @@ async function loadJsonlSnapshotStore(options) {
7255
7746
  }
7256
7747
  throw new JsonlSnapshotStoreCorrupted(`${options.errorLabel} JSONL at line ${index + 1} is not valid JSON`);
7257
7748
  }
7258
- if (!isRecord$4(parsed) || parsed.version !== options.version) throw new JsonlSnapshotStoreCorrupted(`unsupported ${options.errorLabel} version at line ${index + 1}`);
7259
- 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)) {
7260
7751
  if (!Array.isArray(parsed.snapshot.records)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} snapshot at line ${index + 1}`);
7261
7752
  latest.clear();
7262
7753
  for (const value of parsed.snapshot.records) {
@@ -7320,7 +7811,7 @@ function validateSessionSequence(previous, record, _lineNumber) {
7320
7811
  return isValidBackgroundSessionTransition(previous, record);
7321
7812
  }
7322
7813
  function isBackgroundSessionState(value) {
7323
- if (!isRecord$4(value)) return false;
7814
+ if (!isRecord$5(value)) return false;
7324
7815
  const phases = [
7325
7816
  "queued",
7326
7817
  "running",
@@ -7338,14 +7829,14 @@ function isBackgroundSessionState(value) {
7338
7829
  if (!Number.isInteger(value.revision) || value.revision < 1) return false;
7339
7830
  if (typeof value.parentRunId !== "string" || value.parentRunId.trim() === "") return false;
7340
7831
  if (typeof value.sourceMessageId !== "string" || value.sourceMessageId.trim() === "") return false;
7341
- if (!isRecord$4(value.origin)) return false;
7832
+ if (!isRecord$5(value.origin)) return false;
7342
7833
  const origin = value.origin;
7343
7834
  if (typeof origin.endpointId !== "string" || origin.endpointId.trim() === "") return false;
7344
7835
  if (typeof origin.tenantKey !== "string") return false;
7345
7836
  if (!Array.isArray(origin.allowedActorOpenIds) || origin.allowedActorOpenIds.some((id) => typeof id !== "string")) return false;
7346
7837
  if (origin.conversationId !== void 0 && typeof origin.conversationId !== "string") return false;
7347
- if (origin.memory !== void 0 && !isRecord$4(origin.memory)) return false;
7348
- 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;
7349
7840
  const authority = value.authority;
7350
7841
  if (typeof authority.agentId !== "string" || authority.agentId.trim() === "") return false;
7351
7842
  if (typeof authority.profileRevision !== "string" || authority.profileRevision.trim() === "") return false;
@@ -7368,17 +7859,17 @@ function isBackgroundSessionState(value) {
7368
7859
  if (typeof value.updatedAt !== "string" || !isIso(value.updatedAt)) return false;
7369
7860
  if (value.lease !== void 0 && !isLease(value.lease)) return false;
7370
7861
  if (value.cancellation !== void 0) {
7371
- if (!isRecord$4(value.cancellation)) return false;
7862
+ if (!isRecord$5(value.cancellation)) return false;
7372
7863
  if (typeof value.cancellation.reason !== "string" || !isIso(value.cancellation.requestedAt)) return false;
7373
7864
  }
7374
7865
  if (value.result !== void 0) {
7375
- if (!isRecord$4(value.result)) return false;
7866
+ if (!isRecord$5(value.result)) return false;
7376
7867
  if (typeof value.result.text !== "string" || typeof value.result.stepRunId !== "string" || !isIso(value.result.completedAt)) return false;
7377
7868
  }
7378
7869
  return true;
7379
7870
  }
7380
7871
  function isLease(value) {
7381
- if (!isRecord$4(value)) return false;
7872
+ if (!isRecord$5(value)) return false;
7382
7873
  return typeof value.owner === "string" && value.owner.trim() !== "" && Number.isInteger(value.epoch) && value.epoch >= 0 && isIso(value.expiresAt);
7383
7874
  }
7384
7875
  function isIso(value) {
@@ -7407,7 +7898,7 @@ function validateDeliverySequence(previous, record, _lineNumber) {
7407
7898
  return previous.state === "pending" && record.state === "delivered" ? void 0 : "transition";
7408
7899
  }
7409
7900
  function isDeliveryRecord(value) {
7410
- if (!isRecord$4(value)) return false;
7901
+ if (!isRecord$5(value)) return false;
7411
7902
  if (typeof value.deliveryId !== "string" || value.deliveryId.trim() === "" || typeof value.sessionId !== "string" || value.sessionId.trim() === "") return false;
7412
7903
  if (typeof value.kind !== "string" || ![
7413
7904
  "progress",
@@ -7737,10 +8228,13 @@ function createProjectMemoryPromptPreparer(options) {
7737
8228
  //#endregion
7738
8229
  //#region src/application/agent/agent-loop-prompt-transformer.ts
7739
8230
  function createAgentLoopPromptTransformer(options) {
7740
- return { run: (input) => Stream.unwrap(Effect.promise(() => Promise.resolve(options.transform(input))).pipe(Effect.map((text) => options.loop.run({
7741
- ...input,
7742
- text
7743
- })))) };
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
+ };
7744
8238
  }
7745
8239
  //#endregion
7746
8240
  //#region src/application/project/project-skill-catalog.ts
@@ -7785,7 +8279,7 @@ async function load(filePath) {
7785
8279
  for (const [index, line] of raw.split("\n").entries()) {
7786
8280
  if (!line.trim()) continue;
7787
8281
  const envelope = JSON.parse(line);
7788
- 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}`);
7789
8283
  const snapshot = envelope.snapshot;
7790
8284
  const key = snapshotKey(snapshot);
7791
8285
  const previous = latest.get(key);
@@ -7796,7 +8290,7 @@ async function load(filePath) {
7796
8290
  return [...latest.values()];
7797
8291
  }
7798
8292
  function isMemorySnapshot(value) {
7799
- 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;
7800
8294
  const binding = value.binding;
7801
8295
  const record = value.record;
7802
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) && [
@@ -8241,7 +8735,7 @@ async function readSnapshot$1(filePath) {
8241
8735
  version: 3
8242
8736
  });
8243
8737
  const value = JSON.parse(raw);
8244
- 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");
8245
8739
  return Object.freeze({
8246
8740
  records: Object.freeze(value.records.map(readRecord)),
8247
8741
  version: value.version
@@ -8254,7 +8748,7 @@ async function writeSnapshot(filePath, records) {
8254
8748
  });
8255
8749
  }
8256
8750
  function readRecord(value) {
8257
- 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");
8258
8752
  for (const field of [
8259
8753
  "automationId",
8260
8754
  "mandateId",
@@ -8668,21 +9162,6 @@ function createRoutedHumanInteractionToolApprovalService(registry) {
8668
9162
  } };
8669
9163
  }
8670
9164
  //#endregion
8671
- //#region src/infrastructure/feishu/feishu-text-reply.ts
8672
- function createConfiguredFeishuTextReplySender(options) {
8673
- const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
8674
- return { reply: (messageId, text) => Effect.gen(function* () {
8675
- yield* options.client.request({
8676
- body: {
8677
- content: JSON.stringify({ text }),
8678
- msg_type: "text"
8679
- },
8680
- method: "POST",
8681
- url: `${baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/reply`
8682
- }, "Feishu reply failed");
8683
- }) };
8684
- }
8685
- //#endregion
8686
9165
  //#region src/infrastructure/feishu/feishu-automation-card-sender.ts
8687
9166
  function createConfiguredFeishuAutomationCardSender(options) {
8688
9167
  const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
@@ -8848,19 +9327,19 @@ async function loadSnapshots(filePath) {
8848
9327
  return interactions;
8849
9328
  }
8850
9329
  function readSnapshot(value, line) {
8851
- 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}`);
8852
9331
  const interaction = value.interaction;
8853
9332
  if (!isHumanInteraction(interaction)) throw new Error(`invalid human interaction snapshot at line ${line}`);
8854
9333
  return structuredClone(interaction);
8855
9334
  }
8856
9335
  function isHumanInteraction(value) {
8857
- 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;
8858
9337
  if (value.kind === "tool-approval") return isToolApprovalRequest(value.request) && isToolApprovalState(value.state) && stateRespectsInteraction(value.state, value);
8859
9338
  if (value.kind === "user-decision") return isUserDecisionOptions(value.options, value.recommendedOptionId) && isUserDecisionState(value.state, value.options) && stateRespectsInteraction(value.state, value);
8860
9339
  return false;
8861
9340
  }
8862
9341
  function isToolApprovalRequest(value) {
8863
- 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");
8864
9343
  }
8865
9344
  function isToolApprovalState(state) {
8866
9345
  const common = isCommonInteractionState(state);
@@ -8874,7 +9353,7 @@ function isUserDecisionState(state, options) {
8874
9353
  const common = isCommonInteractionState(state);
8875
9354
  if (common !== void 0) return common;
8876
9355
  switch (state.status) {
8877
- 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);
8878
9357
  default: return false;
8879
9358
  }
8880
9359
  }
@@ -8889,14 +9368,14 @@ function isCommonInteractionState(state) {
8889
9368
  }
8890
9369
  function isUserDecisionOptions(value, recommendedOptionId) {
8891
9370
  if (!Array.isArray(value) || value.length < 2 || value.length > 5) return false;
8892
- const ids = value.map((option) => isRecord$4(option) ? option.id : void 0);
8893
- 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));
8894
9373
  }
8895
9374
  function isFacts(value) {
8896
- 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"));
8897
9376
  }
8898
9377
  function isActor(value) {
8899
- return isRecord$4(value) && nonEmptyStrings(value, "openId", "tenantKey");
9378
+ return isRecord$5(value) && nonEmptyStrings(value, "openId", "tenantKey");
8900
9379
  }
8901
9380
  function isNonEmptyStringArray(value) {
8902
9381
  return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string" && entry.length > 0);
@@ -8911,7 +9390,7 @@ function isTimestamp(value) {
8911
9390
  return typeof value === "string" && Number.isFinite(Date.parse(value));
8912
9391
  }
8913
9392
  function stateRespectsInteraction(state, interaction) {
8914
- if (isRecord$4(state.actor)) {
9393
+ if (isRecord$5(state.actor)) {
8915
9394
  if (state.actor.tenantKey !== interaction.tenantKey || !Array.isArray(interaction.allowedActorOpenIds) || !interaction.allowedActorOpenIds.includes(state.actor.openId)) return false;
8916
9395
  }
8917
9396
  const expiresAt = interaction.expiresAt;
@@ -9087,4 +9566,4 @@ function validateDecisionInput(input) {
9087
9566
  if (input.recommendedOptionId && !optionIds.includes(input.recommendedOptionId)) throw new Error("recommended user decision option must be available");
9088
9567
  }
9089
9568
  //#endregion
9090
- 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, createFeishuSessionStore, 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, openJsonFeishuSessionStore, 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 };