@ycodium-ai/plugin-im 0.2.3214 → 0.2.3220

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.
Files changed (2) hide show
  1. package/dist/index.js +449 -216
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -126258,67 +126258,96 @@ var keepNewest = (record4, limit) => {
126258
126258
  return entries.length <= limit ? record4 : Object.fromEntries(entries.slice(entries.length - limit));
126259
126259
  };
126260
126260
 
126261
- // src/boundedMap.ts
126262
- var makeBoundedMap = (limit) => {
126263
- const entries = /* @__PURE__ */ new Map();
126264
- return {
126265
- get: (key) => entries.get(key),
126266
- set: (key, value2) => {
126267
- entries.delete(key);
126268
- entries.set(key, value2);
126269
- if (entries.size > limit) {
126270
- const oldest = entries.keys().next().value;
126271
- if (oldest !== void 0) entries.delete(oldest);
126272
- }
126273
- },
126274
- /** 忘掉键满足条件的那些。 */
126275
- forget: (matches) => {
126276
- for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
126261
+ // src/executionTurn.ts
126262
+ var indexExecution = (execution) => {
126263
+ const intentsByMessageId = /* @__PURE__ */ new Map();
126264
+ const intentsById = /* @__PURE__ */ new Map();
126265
+ const turnsById = /* @__PURE__ */ new Map();
126266
+ for (const intent of execution.turnIntents) {
126267
+ if (!intentsByMessageId.has(intent.messageId)) intentsByMessageId.set(intent.messageId, intent);
126268
+ if (!intentsById.has(intent.intentId)) intentsById.set(intent.intentId, intent);
126269
+ if (intent.turn?.turnId != null && !turnsById.has(intent.turn.turnId)) {
126270
+ turnsById.set(intent.turn.turnId, intent.turn);
126277
126271
  }
126278
- };
126272
+ }
126273
+ if (execution.latestTurn != null && !turnsById.has(execution.latestTurn.turnId)) {
126274
+ turnsById.set(execution.latestTurn.turnId, execution.latestTurn);
126275
+ }
126276
+ return { execution, intentsByMessageId, intentsById, turnsById };
126279
126277
  };
126278
+ var executionTurnFor = (snapshot, turnId) => snapshot.turnsById.get(turnId);
126280
126279
 
126281
- // src/replyAnchors.ts
126282
- var THREAD_LIMIT = 256;
126283
- var TURNS_PER_THREAD = 16;
126284
- var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
126285
- var userMessageIdOfTurn = (execution, turnId) => {
126286
- const intent = execution.turnIntents.find((candidate) => candidate.turn?.turnId === turnId);
126287
- const latest = execution.latestTurn;
126288
- return intent?.messageId ?? (latest?.turnId === turnId ? latest.userMessageId : void 0);
126280
+ // src/intentStatus.ts
126281
+ var isLiveIntentStatus = (status) => status === "queued" || status === "preparing" || status === "dispatching" || status === "running" || status === "reconciling" || status === "blocked";
126282
+
126283
+ // src/conclusionTurn.ts
126284
+ var conclusionOf = (turn) => turn.state === "completed" || turn.state === "interrupted" || turn.state === "error" ? { kind: "settled", ...turn, state: turn.state } : { kind: "pending" };
126285
+ var conclusionTurnFor = (snapshot, userMessageId) => {
126286
+ const { execution } = snapshot;
126287
+ const initial = snapshot.intentsByMessageId.get(userMessageId);
126288
+ if (initial === void 0) {
126289
+ const latest = execution.latestTurn;
126290
+ return latest?.userMessageId === userMessageId ? conclusionOf(latest) : { kind: "gone" };
126291
+ }
126292
+ let intent = initial;
126293
+ const visited = /* @__PURE__ */ new Set();
126294
+ while (intent.status === "cancelled" || intent.status === "abandoned") {
126295
+ if (visited.has(intent.intentId)) return { kind: "gone" };
126296
+ visited.add(intent.intentId);
126297
+ const replacementId = intent.replacementIntentId;
126298
+ const replacement = replacementId == null ? void 0 : snapshot.intentsById.get(replacementId);
126299
+ if (replacement === void 0) return { kind: "gone" };
126300
+ intent = replacement;
126301
+ }
126302
+ const action = intent.action;
126303
+ const turn = intent.turn;
126304
+ if (action?.kind === "steer" && intent.deliveredTurnId === action.turnId) {
126305
+ return {
126306
+ kind: "merged",
126307
+ turnId: action.turnId,
126308
+ turn: executionTurnFor(snapshot, action.turnId)
126309
+ };
126310
+ }
126311
+ if (turn !== null) return conclusionOf(turn);
126312
+ if (action?.kind === "steer") return { kind: "pending" };
126313
+ return { kind: isLiveIntentStatus(intent.status) ? "pending" : "gone" };
126289
126314
  };
126290
- var makeReplyAnchors = (host) => {
126291
- const byThread = makeBoundedMap(THREAD_LIMIT);
126292
- const userMessageOfTurn = async (threadId, turnId) => {
126293
- const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
126294
- if (execution === null) return void 0;
126295
- const wanted = turnId ?? execution.session?.activeTurnId ?? execution.latestTurn?.turnId;
126296
- return wanted == null ? void 0 : userMessageIdOfTurn(execution, wanted);
126297
- };
126315
+ var groupConclusionDebts = (snapshot, threadId, debts) => {
126316
+ const resolved = new Map(
126317
+ debts.filter((debt) => debt.threadId === threadId).map((debt) => [
126318
+ debt.userMessageId,
126319
+ { debt, result: conclusionTurnFor(snapshot, debt.userMessageId) }
126320
+ ])
126321
+ );
126322
+ const byTurn = /* @__PURE__ */ new Map();
126323
+ for (const { debt, result: result2 } of resolved.values()) {
126324
+ if (result2.kind !== "settled" && result2.kind !== "merged") continue;
126325
+ const group = byTurn.get(result2.turnId) ?? [];
126326
+ group.push(debt);
126327
+ byTurn.set(result2.turnId, group);
126328
+ }
126329
+ for (const [turnId, candidates] of byTurn) {
126330
+ const ownerId = turnId === null ? void 0 : executionTurnFor(snapshot, turnId)?.userMessageId;
126331
+ const owner = ownerId === void 0 ? void 0 : resolved.get(ownerId);
126332
+ if (owner !== void 0 && owner.result.kind !== "gone" && !candidates.includes(owner.debt))
126333
+ candidates.push(owner.debt);
126334
+ }
126335
+ return { resolved, byTurn };
126336
+ };
126337
+ var conclusionDebtForTurn = (snapshot, turnId, candidates, isDelivered) => {
126338
+ const turn = turnId === null ? void 0 : executionTurnFor(snapshot, turnId);
126339
+ const own = candidates.find((debt2) => debt2.userMessageId === turn?.userMessageId);
126340
+ const handled = turn?.userMessageId !== void 0 && isDelivered(turn.userMessageId) || candidates.length === 0 && snapshot.execution.turnIntents.some((intent) => {
126341
+ if (!isDelivered(intent.messageId)) return false;
126342
+ const result2 = conclusionTurnFor(snapshot, intent.messageId);
126343
+ return (result2.kind === "merged" || result2.kind === "settled") && result2.turnId === turnId;
126344
+ });
126345
+ const debt = handled ? void 0 : own ?? candidates[0];
126298
126346
  return {
126299
- /**
126300
- * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
126301
- * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
126302
- */
126303
- note: (threadId, userMessageId, binding) => {
126304
- if (binding.reply === void 0) return;
126305
- const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
126306
- byThread.set(threadId, turns);
126307
- turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
126308
- },
126309
- /**
126310
- * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
126311
- *
126312
- * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
126313
- * 回复的消息寻址,挂错了答复就发回原来的聊天。
126314
- */
126315
- anchored: async (binding, threadId, turnId) => {
126316
- const turns = byThread.get(threadId);
126317
- if (turns === void 0) return binding;
126318
- const asked = await userMessageOfTurn(threadId, turnId);
126319
- const noted = asked === void 0 ? void 0 : turns.get(asked);
126320
- return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
126321
- }
126347
+ debt,
126348
+ handled,
126349
+ userMessageId: turn?.userMessageId,
126350
+ redundant: candidates.filter((candidate) => candidate !== debt)
126322
126351
  };
126323
126352
  };
126324
126353
 
@@ -126336,8 +126365,19 @@ var foldConclusionEvent = (state, name, data) => {
126336
126365
  const pending = conclusionDebtsOf(state);
126337
126366
  if (name === "conclusion-delivered") {
126338
126367
  const { [key]: _delivered, ...rest } = pending;
126339
- return { ...state, conclusions: rest };
126368
+ const turnUserMessageId = data["turnUserMessageId"];
126369
+ const turnKey2 = typeof turnUserMessageId === "string" && turnUserMessageId !== "" ? keyOf(threadId, turnUserMessageId) : key;
126370
+ const { [turnKey2]: _turnDelivered, ...remaining } = rest;
126371
+ return {
126372
+ ...state,
126373
+ conclusions: remaining,
126374
+ deliveredConclusions: keepNewest(
126375
+ { ...state.deliveredConclusions, [key]: true, [turnKey2]: true },
126376
+ CONCLUSION_DEBT_LIMIT
126377
+ )
126378
+ };
126340
126379
  }
126380
+ if (state.deliveredConclusions?.[key] === true) return state;
126341
126381
  const chatKey = data["chatKey"];
126342
126382
  const recordedAt = data["recordedAt"];
126343
126383
  if (typeof chatKey !== "string" || chatKey === "" || typeof recordedAt !== "string" || !Number.isFinite(Date.parse(recordedAt))) {
@@ -126354,19 +126394,41 @@ var foldConclusionEvent = (state, name, data) => {
126354
126394
  var makeConclusionLedger = (core) => {
126355
126395
  const inFlight = /* @__PURE__ */ new Set();
126356
126396
  const registering = /* @__PURE__ */ new Map();
126357
- const delivered = makeBoundedMap(CONCLUSION_DEBT_LIMIT);
126397
+ const unpersisted = /* @__PURE__ */ new Set();
126398
+ const delivered = (key) => core.state.deliveredConclusions?.[key] === true;
126358
126399
  let disposed = false;
126359
126400
  core.host.onDispose(() => {
126360
126401
  disposed = true;
126361
126402
  });
126362
- const settle = (debt) => {
126363
- delivered.set(keyOf(debt.threadId, debt.userMessageId), true);
126403
+ const settle = ({ debt, turnUserMessageId }) => {
126364
126404
  core.commit("conclusion-delivered", {
126365
126405
  threadId: debt.threadId,
126366
- userMessageId: debt.userMessageId
126406
+ userMessageId: debt.userMessageId,
126407
+ ...turnUserMessageId !== void 0 && turnUserMessageId !== debt.userMessageId ? { turnUserMessageId } : {}
126367
126408
  });
126368
126409
  };
126410
+ const forExecutionTurn = (snapshot, threadId, turnId, grouped) => {
126411
+ if (disposed) return { selection: void 0, handled: true };
126412
+ const pending = conclusionDebtsOf(core.state);
126413
+ const candidates = (grouped ?? groupConclusionDebts(snapshot, threadId, Object.values(pending))).byTurn.get(turnId);
126414
+ const currentCandidates = candidates?.filter(
126415
+ (debt) => pending[keyOf(threadId, debt.userMessageId)] !== void 0
126416
+ );
126417
+ if (currentCandidates?.length === 0) return { selection: void 0, handled: true };
126418
+ const selected = conclusionDebtForTurn(
126419
+ snapshot,
126420
+ turnId,
126421
+ currentCandidates ?? [],
126422
+ (messageId) => delivered(keyOf(threadId, messageId))
126423
+ );
126424
+ for (const debt of selected.redundant) settle({ debt, turnUserMessageId: void 0 });
126425
+ return {
126426
+ selection: selected.debt === void 0 ? void 0 : { debt: selected.debt, turnUserMessageId: selected.userMessageId },
126427
+ handled: selected.handled || selected.debt !== void 0 && inFlight.has(keyOf(threadId, selected.debt.userMessageId))
126428
+ };
126429
+ };
126369
126430
  return {
126431
+ forExecutionTurn,
126370
126432
  starting: (threadId) => {
126371
126433
  const gate = Promise.withResolvers();
126372
126434
  const pending = registering.get(threadId) ?? /* @__PURE__ */ new Set();
@@ -126378,44 +126440,59 @@ var makeConclusionLedger = (core) => {
126378
126440
  gate.resolve();
126379
126441
  };
126380
126442
  },
126381
- owe: (debt) => {
126443
+ /** 重放才传 recover;只对本次成功补出的欠账调度恢复,已有欠账交给原来的收尾路径。 */
126444
+ owe: async (debt, recover) => {
126445
+ const key = keyOf(debt.threadId, debt.userMessageId);
126446
+ if (delivered(key)) {
126447
+ core.host.log.info("chat skipped a conclusion debt already delivered", { ...debt });
126448
+ return;
126449
+ }
126382
126450
  const pending = conclusionDebtsOf(core.state);
126383
- if (Object.keys(pending).length >= CONCLUSION_DEBT_LIMIT && pending[keyOf(debt.threadId, debt.userMessageId)] === void 0) {
126451
+ const added = pending[key] === void 0 || unpersisted.has(key);
126452
+ if (Object.keys(pending).length >= CONCLUSION_DEBT_LIMIT && pending[key] === void 0) {
126384
126453
  core.host.log.warn("chat dropped its oldest conclusion debt at capacity", {
126385
126454
  oldest: Object.values(pending)[0],
126386
126455
  limit: CONCLUSION_DEBT_LIMIT
126387
126456
  });
126388
126457
  }
126389
- core.commit("conclusion-owed", { ...debt });
126458
+ if (added) unpersisted.add(key);
126459
+ await core.commitDurably("conclusion-owed", { ...debt });
126460
+ unpersisted.delete(key);
126461
+ if (added) await recover?.(debt);
126390
126462
  },
126391
126463
  forTurn: async (threadId, turnId = null) => {
126392
126464
  await Promise.all(registering.get(threadId) ?? []);
126393
126465
  const execution = await core.host.projections.getThreadExecution(threadId);
126394
- const userMessageId = execution === null ? void 0 : turnId === null ? execution.latestTurn?.userMessageId : userMessageIdOfTurn(execution, turnId);
126395
- const key = userMessageId === void 0 ? void 0 : keyOf(threadId, userMessageId);
126396
126466
  return {
126397
- debt: key === void 0 ? void 0 : conclusionDebtsOf(core.state)[key],
126398
- handled: key !== void 0 && (delivered.get(key) === true || inFlight.has(key)),
126399
- execution
126467
+ execution,
126468
+ ...execution === null ? { selection: void 0, handled: false } : forExecutionTurn(
126469
+ indexExecution(execution),
126470
+ threadId,
126471
+ turnId ?? execution.latestTurn?.turnId ?? null
126472
+ )
126400
126473
  };
126401
126474
  },
126402
- deliver: async (debt, send) => {
126475
+ deliver: async (selection, send) => {
126476
+ const { debt } = selection;
126403
126477
  const key = keyOf(debt.threadId, debt.userMessageId);
126404
- if (disposed || inFlight.has(key) || conclusionDebtsOf(core.state)[key] === void 0)
126478
+ if (disposed || delivered(key) || inFlight.has(key) || conclusionDebtsOf(core.state)[key] === void 0)
126405
126479
  return false;
126406
126480
  inFlight.add(key);
126407
126481
  try {
126408
- const sent = await send(() => conclusionDebtsOf(core.state)[key] !== void 0);
126409
- if (sent) settle(debt);
126482
+ const sent = await send(
126483
+ () => !delivered(key) && conclusionDebtsOf(core.state)[key] !== void 0
126484
+ );
126485
+ if (sent) settle(selection);
126410
126486
  return sent;
126411
126487
  } finally {
126412
126488
  inFlight.delete(key);
126413
126489
  }
126414
126490
  },
126415
- discard: (debt) => {
126491
+ discard: (selection) => {
126492
+ const { debt } = selection;
126416
126493
  const key = keyOf(debt.threadId, debt.userMessageId);
126417
- if (!disposed && !inFlight.has(key) && conclusionDebtsOf(core.state)[key] !== void 0) {
126418
- settle(debt);
126494
+ if (!disposed && !delivered(key) && !inFlight.has(key) && conclusionDebtsOf(core.state)[key] !== void 0) {
126495
+ settle(selection);
126419
126496
  }
126420
126497
  }
126421
126498
  };
@@ -127430,6 +127507,26 @@ var makeChatQueue = (core) => {
127430
127507
  // src/chatSetup.ts
127431
127508
  import { threadOrNull } from "@ycodium-ai/plugin-api";
127432
127509
 
127510
+ // src/boundedMap.ts
127511
+ var makeBoundedMap = (limit) => {
127512
+ const entries = /* @__PURE__ */ new Map();
127513
+ return {
127514
+ get: (key) => entries.get(key),
127515
+ set: (key, value2) => {
127516
+ entries.delete(key);
127517
+ entries.set(key, value2);
127518
+ if (entries.size > limit) {
127519
+ const oldest = entries.keys().next().value;
127520
+ if (oldest !== void 0) entries.delete(oldest);
127521
+ }
127522
+ },
127523
+ /** 忘掉键满足条件的那些。 */
127524
+ forget: (matches) => {
127525
+ for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
127526
+ }
127527
+ };
127528
+ };
127529
+
127433
127530
  // src/cooldown.ts
127434
127531
  var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
127435
127532
  const lastAt = /* @__PURE__ */ new Map();
@@ -133584,18 +133681,14 @@ var makeInboundStore = (input) => {
133584
133681
  write(rowId, row);
133585
133682
  return result2.promise;
133586
133683
  };
133587
- const claim = (rowId, id, message, receivedAt = 0) => {
133684
+ const claimReceipt = (rowId, id, message, receivedAt = 0) => {
133588
133685
  const row = rowOf(rowId);
133589
- if (!row.loaded) return load(rowId).then(() => claim(rowId, id, message, receivedAt));
133590
- try {
133591
- assertReadable(row);
133592
- } catch (error) {
133593
- return Promise.reject(error);
133594
- }
133686
+ assertReadable(row);
133595
133687
  const pending = row.claims.get(id);
133596
- if (pending !== void 0) return pending.then(() => false);
133688
+ if (pending !== void 0) return { first: false, persisted: pending };
133597
133689
  const seen = row.data.seenIds.includes(id);
133598
- if (seen && (row.claimRevisions.get(id) ?? 0) <= row.persisted) return Promise.resolve(false);
133690
+ if (seen && (row.claimRevisions.get(id) ?? 0) <= row.persisted)
133691
+ return { first: false, persisted: Promise.resolve() };
133599
133692
  if (!seen) {
133600
133693
  if (message !== void 0) {
133601
133694
  const inbox = row.data.inbox ?? [];
@@ -133639,7 +133732,17 @@ var makeInboundStore = (input) => {
133639
133732
  row.claims.delete(id);
133640
133733
  };
133641
133734
  void result2.then(forget, forget);
133642
- return result2.then(() => !seen);
133735
+ return { first: !seen, persisted: result2 };
133736
+ };
133737
+ const claim = (rowId, id, message, receivedAt = 0) => {
133738
+ const row = rowOf(rowId);
133739
+ if (!row.loaded) return load(rowId).then(() => claim(rowId, id, message, receivedAt));
133740
+ try {
133741
+ const receipt = claimReceipt(rowId, id, message, receivedAt);
133742
+ return receipt.persisted.then(() => receipt.first);
133743
+ } catch (error) {
133744
+ return Promise.reject(error);
133745
+ }
133643
133746
  };
133644
133747
  const removeEntry = (rowId, key, failure2) => {
133645
133748
  const row = rowOf(rowId);
@@ -133765,6 +133868,7 @@ var makeInboundStore = (input) => {
133765
133868
  return {
133766
133869
  load,
133767
133870
  claim,
133871
+ claimReceipt,
133768
133872
  get: (rowId, key) => {
133769
133873
  const row = rowOf(rowId);
133770
133874
  assertReadable(row);
@@ -133802,6 +133906,8 @@ var makeInboundStore = (input) => {
133802
133906
  assertReadable(row);
133803
133907
  if (row.data.inbox !== void 0)
133804
133908
  row.data.inbox = row.data.inbox.filter((e) => e.key !== key);
133909
+ row.data.seenIds = row.data.seenIds.filter((id) => id !== key);
133910
+ row.claimRevisions.delete(key);
133805
133911
  row.revision++;
133806
133912
  },
133807
133913
  reset,
@@ -133942,7 +134048,9 @@ var startConnections = (core, inboundHandlersFor, inboundState, recovery) => {
133942
134048
  ...inbound,
133943
134049
  // 停用先封接收,保持发送连接直到已接收工作 drain 完成。
133944
134050
  onMessage: (message) => {
133945
- if (!closed && !disposed) inbound.onMessage(message);
134051
+ if (closed || disposed)
134052
+ return Promise.reject(new Error("inbound connection is closed"));
134053
+ return inbound.onMessage(message);
133946
134054
  },
133947
134055
  onButton: (...args2) => closed || disposed ? null : inbound.onButton(...args2),
133948
134056
  onPanelClick: (click) => closed || disposed ? null : inbound.onPanelClick?.(click) ?? null,
@@ -134339,16 +134447,18 @@ var makeImCore = (input) => {
134339
134447
  }
134340
134448
  );
134341
134449
  let persisting = Promise.resolve();
134342
- const commit = (name, data) => {
134450
+ const commitDurably = (name, data) => {
134343
134451
  state = foldImEvent(state, name, data);
134344
- persisting = persisting.then(
134452
+ const saved = persisting.then(
134345
134453
  () => host.events.append({ name, aggregate: { kind: "plugin", id: input.pluginId }, data })
134346
- ).then(
134347
- () => void 0,
134348
- (error) => {
134349
- host.log.warn("chat could not save its state", { name, error: describeError(error) });
134350
- }
134351
- );
134454
+ ).then(() => void 0);
134455
+ persisting = saved.catch((error) => {
134456
+ host.log.warn("chat could not save its state", { name, error: describeError(error) });
134457
+ });
134458
+ return saved;
134459
+ };
134460
+ const commit = (name, data) => {
134461
+ void commitDurably(name, data);
134352
134462
  };
134353
134463
  const bindChat = (chatKey, threadId) => {
134354
134464
  commit("chat-updated", { chatKey, threadId });
@@ -134414,6 +134524,7 @@ var makeImCore = (input) => {
134414
134524
  },
134415
134525
  ready,
134416
134526
  commit,
134527
+ commitDurably,
134417
134528
  bindChat,
134418
134529
  outbound,
134419
134530
  deliver,
@@ -134885,6 +134996,7 @@ import { projectOrNull } from "@ycodium-ai/plugin-api";
134885
134996
 
134886
134997
  // src/inboxProcessing.ts
134887
134998
  var processingFor = (store, rowId, entry, retry) => ({
134999
+ replayed: entry.replayed === true,
134888
135000
  get entry() {
134889
135001
  return store.get(rowId, entry.key);
134890
135002
  },
@@ -135046,7 +135158,7 @@ var makeInboxReplay = (core, currentBot, state, enqueueForChat, resumeMessage, c
135046
135158
  await resumeMessage(
135047
135159
  bot,
135048
135160
  latest.message === entry.message ? message : restoreInbound(core.adapterFor(bot.platform), latest.message),
135049
- latest,
135161
+ { ...latest, replayed: true },
135050
135162
  chatId
135051
135163
  );
135052
135164
  });
@@ -135085,6 +135197,45 @@ var makeGreetings = (core, enqueueForChat, now) => {
135085
135197
  };
135086
135198
  };
135087
135199
 
135200
+ // src/replyAnchors.ts
135201
+ var THREAD_LIMIT = 256;
135202
+ var TURNS_PER_THREAD = 16;
135203
+ var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
135204
+ var makeReplyAnchors = (host) => {
135205
+ const byThread = makeBoundedMap(THREAD_LIMIT);
135206
+ const userMessageOfTurn = async (threadId, turnId) => {
135207
+ const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
135208
+ if (execution === null) return void 0;
135209
+ const wanted = turnId ?? execution.session?.activeTurnId ?? execution.latestTurn?.turnId;
135210
+ return wanted == null ? void 0 : executionTurnFor(indexExecution(execution), wanted)?.userMessageId;
135211
+ };
135212
+ return {
135213
+ /**
135214
+ * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
135215
+ * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
135216
+ */
135217
+ note: (threadId, userMessageId, binding) => {
135218
+ if (binding.reply === void 0) return;
135219
+ const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
135220
+ byThread.set(threadId, turns);
135221
+ turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
135222
+ },
135223
+ /**
135224
+ * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
135225
+ *
135226
+ * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
135227
+ * 回复的消息寻址,挂错了答复就发回原来的聊天。
135228
+ */
135229
+ anchored: async (binding, threadId, turnId) => {
135230
+ const turns = byThread.get(threadId);
135231
+ if (turns === void 0) return binding;
135232
+ const asked = await userMessageOfTurn(threadId, turnId);
135233
+ const noted = asked === void 0 ? void 0 : turns.get(asked);
135234
+ return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
135235
+ }
135236
+ };
135237
+ };
135238
+
135088
135239
  // src/inbound.ts
135089
135240
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
135090
135241
  var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
@@ -135312,40 +135463,44 @@ var makeInbound = (core, deps) => {
135312
135463
  noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
135313
135464
  knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
135314
135465
  onGreeting: (chatId, senderId) => greet(bot.rowId, chatId, senderId),
135315
- onMessage: (received) => {
135466
+ onMessage: async (received) => {
135316
135467
  const current = currentBot(bot.rowId);
135317
- if (!accepting || current === void 0) return;
135468
+ if (!accepting || current === void 0) throw new Error("inbound is not accepting messages");
135318
135469
  const message = settleChat(current, received);
135319
135470
  const work = workForMessage(message);
135320
- if (inboundState.hasSeen(current.rowId, work.key)) return;
135471
+ const adapter = adapterFor(current.platform);
135321
135472
  let saved;
135322
135473
  try {
135323
- saved = persistInbound(adapterFor(current.platform), message);
135474
+ saved = persistInbound(adapter, message);
135324
135475
  } catch (error) {
135325
135476
  host.log.warn("chat could not serialize inbound; processing without restart protection", {
135326
135477
  rowId: current.rowId,
135327
135478
  error: describeError(error)
135328
135479
  });
135329
135480
  }
135481
+ const claim = inboundState.claimReceipt(current.rowId, work.key, saved, now());
135482
+ if (!claim.first) return claim.persisted;
135483
+ const receipt = claim.persisted.catch((error) => {
135484
+ inboundState.abandon(current.rowId, work.key);
135485
+ host.log.warn(
135486
+ adapter?.redeliversUnacknowledged ? "chat could not save inbound state; waiting for platform redelivery" : "chat could not save inbound state; processing without restart protection",
135487
+ { rowId: current.rowId, error: describeError(error) }
135488
+ );
135489
+ throw error;
135490
+ });
135491
+ const enqueue = () => {
135492
+ const arrived = bindingOf2(current, message.chatId);
135493
+ if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
135494
+ commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
135495
+ }
135496
+ enqueueForChat(chatKeyOf(arrived), () => resumeMessage(current, message, work));
135497
+ };
135330
135498
  track(
135331
- inboundState.claim(current.rowId, work.key, saved, now()).then(
135332
- () => {
135333
- },
135334
- (error) => {
135335
- inboundState.abandon(current.rowId, work.key);
135336
- host.log.warn(
135337
- "chat could not save inbound state; processing without restart protection",
135338
- { rowId: current.rowId, error: describeError(error) }
135339
- );
135340
- }
135341
- ).then(() => {
135342
- const arrived = bindingOf2(current, message.chatId);
135343
- if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
135344
- commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
135345
- }
135346
- enqueueForChat(chatKeyOf(arrived), () => resumeMessage(current, message, work));
135499
+ receipt.then(enqueue, () => {
135500
+ if (!adapter?.redeliversUnacknowledged) enqueue();
135347
135501
  })
135348
135502
  );
135503
+ return receipt;
135349
135504
  }
135350
135505
  });
135351
135506
  const replay = makeInboxReplay(
@@ -135779,14 +135934,6 @@ var queueCardOf = (record4, view, note, t) => {
135779
135934
  var queueViewKey = (view, note) => JSON.stringify([view, note]);
135780
135935
 
135781
135936
  // src/queueFlow.ts
135782
- var LIVE_STATUSES = /* @__PURE__ */ new Set([
135783
- "queued",
135784
- "preparing",
135785
- "dispatching",
135786
- "running",
135787
- "reconciling",
135788
- "blocked"
135789
- ]);
135790
135937
  var makeQueueFlow = (host, deps) => {
135791
135938
  const { t } = host;
135792
135939
  const shown = /* @__PURE__ */ new Map();
@@ -135810,7 +135957,7 @@ var makeQueueFlow = (host, deps) => {
135810
135957
  const intent = execution?.intents.find((entry) => entry.messageId === messageId);
135811
135958
  if (execution === null || intent === void 0 || intent.status !== "queued") return;
135812
135959
  const waiting = execution.activeTurnId !== null || execution.queuePaused || execution.intents.some(
135813
- (other) => other.intentId !== intent.intentId && LIVE_STATUSES.has(other.status) && other.sequence < intent.sequence
135960
+ (other) => other.intentId !== intent.intentId && isLiveIntentStatus(other.status) && other.sequence < intent.sequence
135814
135961
  );
135815
135962
  if (!waiting) return;
135816
135963
  const cards = target2.adapter.choiceCards;
@@ -144047,7 +144194,7 @@ var cardActionOperator = (event) => {
144047
144194
  };
144048
144195
  var startLarkInbound = (input) => {
144049
144196
  const dispatcher = new import_node_sdk.EventDispatcher({}).register({
144050
- "im.message.receive_v1": (event) => {
144197
+ "im.message.receive_v1": async (event) => {
144051
144198
  const mode = input.groupMention ?? "always";
144052
144199
  const parse = (groupMention) => parseInboundMessage(
144053
144200
  event,
@@ -144057,17 +144204,16 @@ var startLarkInbound = (input) => {
144057
144204
  );
144058
144205
  const parsed = parse(mode);
144059
144206
  if (parsed !== null) {
144060
- input.onMessage(parsed);
144207
+ await input.onMessage(parsed);
144061
144208
  return {};
144062
144209
  }
144063
144210
  const chat = event.message;
144064
144211
  if (mode === "always" && input.isSoloGroup !== void 0 && chat?.chat_type !== "p2p") {
144065
144212
  const chatId = chat?.chat_id;
144066
144213
  if (typeof chatId === "string") {
144067
- void input.isSoloGroup(chatId).then((solo) => {
144068
- const relaxed = solo ? parse("never") : null;
144069
- if (relaxed !== null) input.onMessage(relaxed);
144070
- });
144214
+ const solo = await input.isSoloGroup(chatId).catch(() => false);
144215
+ const relaxed = solo ? parse("never") : null;
144216
+ if (relaxed !== null) await input.onMessage(relaxed);
144071
144217
  }
144072
144218
  }
144073
144219
  return {};
@@ -144646,10 +144792,12 @@ var makeLarkAdapter = (deps) => {
144646
144792
  };
144647
144793
  const soloGroupFor = (context2) => async (chatId) => {
144648
144794
  const info = await context2.chatInfo(chatId);
144649
- return info?.userCount !== void 0 && info.botCount !== void 0 && info.userCount <= 1 && info.botCount <= 1;
144795
+ if (info?.userCount === void 0 || info.botCount === void 0) return false;
144796
+ return info.userCount <= 1 && info.botCount <= 1;
144650
144797
  };
144651
144798
  return {
144652
144799
  platform: "lark",
144800
+ redeliversUnacknowledged: true,
144653
144801
  ...larkInboundPersistence((rowId) => messageContexts.get(rowId)?.context),
144654
144802
  botIdentity: (bot) => larkCredentials(bot).appId,
144655
144803
  icon: "lark",
@@ -144757,15 +144905,27 @@ var makeLarkAdapter = (deps) => {
144757
144905
  const key = JSON.stringify([message.chatId, topic?.threadId]);
144758
144906
  const earlier = pendingDeliveries.get(key);
144759
144907
  if (earlier === void 0 && pending === null && reference2 === null && !(prepared instanceof Promise) && !(source instanceof Promise)) {
144760
- deliver(prepared, source);
144908
+ return deliver(prepared, source);
144761
144909
  } else {
144762
- const completed = Promise.all([earlier, pending, reference2, source, prepared]).then(([, , replyTo, names, ready]) => {
144910
+ const completed = Promise.all([
144911
+ earlier?.catch(() => {
144912
+ }),
144913
+ pending,
144914
+ reference2,
144915
+ source,
144916
+ prepared
144917
+ ]).then(([, , replyTo, names, ready]) => {
144763
144918
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
144764
- if (open) deliver(ready, names, replyTo ?? void 0);
144765
- }).catch((error) => handlers.onError(describeError(error))).finally(() => {
144919
+ if (!open) throw new Error("lark inbound connection is closed");
144920
+ return deliver(ready, names, replyTo ?? void 0);
144921
+ }).catch((error) => {
144922
+ handlers.onError(describeError(error));
144923
+ throw error;
144924
+ }).finally(() => {
144766
144925
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
144767
144926
  });
144768
144927
  pendingDeliveries.set(key, completed);
144928
+ return completed;
144769
144929
  }
144770
144930
  }
144771
144931
  });
@@ -146619,18 +146779,18 @@ var makeModelPlan = (core, deps) => {
146619
146779
  import { readThreadFailed, threadOrNull as threadOrNull5 } from "@ycodium-ai/plugin-api";
146620
146780
 
146621
146781
  // src/conclusionOutbox.ts
146622
- var enqueueConclusion = (core, ledger, binding, debt, send) => core.outbound(
146782
+ var enqueueConclusion = (core, ledger, binding, selection, send) => core.outbound(
146623
146783
  chatKeyOf(binding),
146624
146784
  async () => {
146625
- if (debt === void 0) await send();
146626
- else await ledger.deliver(debt, send);
146785
+ if (selection === void 0) await send();
146786
+ else await ledger.deliver(selection, send);
146627
146787
  },
146628
- { droppable: debt !== void 0 }
146788
+ { droppable: selection !== void 0 }
146629
146789
  );
146630
146790
 
146631
146791
  // src/conclusionRecovery.ts
146632
146792
  var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
146633
- var recoverConclusions = async (core, ledger, delivery, failures) => {
146793
+ var makeConclusionRecovery = (core, ledger, delivery, failures) => {
146634
146794
  let disposed = false;
146635
146795
  core.host.onDispose(() => {
146636
146796
  disposed = true;
@@ -146641,55 +146801,103 @@ var recoverConclusions = async (core, ledger, delivery, failures) => {
146641
146801
  error: describeError(error)
146642
146802
  });
146643
146803
  };
146644
- await core.ready;
146645
- for (const debt of Object.values(conclusionDebtsOf(core.state))) {
146804
+ const recoverThread = async (threadId, debts, requestedMessageId) => {
146805
+ await core.ready;
146646
146806
  if (disposed) return;
146647
146807
  try {
146648
- const execution = await core.host.projections.getThreadExecution(debt.threadId);
146808
+ const execution = await core.host.projections.getThreadExecution(threadId);
146649
146809
  if (disposed) return;
146650
146810
  if (execution === null) {
146651
- ledger.discard(debt);
146652
- continue;
146653
- }
146654
- const latest = execution.latestTurn;
146655
- if (latest?.userMessageId !== debt.userMessageId) {
146656
- core.host.log.info("chat discarded a conclusion whose turn is no longer latest", {
146657
- ...debt
146658
- });
146659
- ledger.discard(debt);
146660
- continue;
146661
- }
146662
- if (latest.state === "running" || execution.session?.activeTurnId != null) continue;
146663
- if (latest.completedAt !== null && Date.parse(latest.completedAt) < Date.now() - MAX_AGE_MS) {
146664
- core.host.log.info("chat discarded a conclusion older than seven days", { ...debt });
146665
- ledger.discard(debt);
146666
- continue;
146811
+ for (const debt of debts) {
146812
+ if (requestedMessageId === void 0 || debt.userMessageId === requestedMessageId)
146813
+ ledger.discard({ debt, turnUserMessageId: void 0 });
146814
+ }
146815
+ return;
146667
146816
  }
146668
- const binding = core.bindingForChatKey(debt.chatKey);
146669
- if (binding === void 0) continue;
146670
- enqueueConclusion(core, ledger, binding, debt, async (owed) => {
146671
- try {
146672
- if (latest.state === "error") {
146673
- return delivery.sendText(
146674
- binding,
146675
- debt.threadId,
146676
- failures.forFailure(debt.threadId, execution.session?.lastError),
146677
- true,
146678
- latest.turnId,
146679
- owed
146680
- );
146817
+ const snapshot = indexExecution(execution);
146818
+ const grouped = groupConclusionDebts(snapshot, threadId, debts);
146819
+ const { resolved, byTurn } = grouped;
146820
+ for (const { debt, result: result2 } of resolved.values()) {
146821
+ if (result2.kind === "gone") {
146822
+ if (requestedMessageId === void 0 || debt.userMessageId === requestedMessageId) {
146823
+ core.host.log.info("chat discarded a conclusion whose message has no turn", {
146824
+ ...debt
146825
+ });
146826
+ ledger.discard({ debt, turnUserMessageId: void 0 });
146681
146827
  }
146682
- const send = await delivery.prepareConclusion(debt.threadId, latest.turnId);
146683
- return send(binding, { recovered: true, streaming: false, owed });
146684
- } catch (error) {
146685
- couldNotRecover(debt, error);
146686
- return false;
146687
146828
  }
146688
- });
146829
+ }
146830
+ const requested = requestedMessageId === void 0 ? void 0 : resolved.get(requestedMessageId)?.result;
146831
+ for (const turnId of byTurn.keys()) {
146832
+ if (requestedMessageId !== void 0 && (requested?.kind !== "settled" && requested?.kind !== "merged" || requested.turnId !== turnId))
146833
+ continue;
146834
+ const { selection, handled } = ledger.forExecutionTurn(snapshot, threadId, turnId, grouped);
146835
+ if (handled || selection === void 0 || requestedMessageId !== void 0 && selection.debt.userMessageId !== requestedMessageId)
146836
+ continue;
146837
+ const result2 = resolved.get(selection.debt.userMessageId).result;
146838
+ if (result2.kind === "pending" || result2.kind === "gone") continue;
146839
+ recoverTurn(selection, result2.kind === "settled" ? result2 : result2.turn, execution);
146840
+ }
146689
146841
  } catch (error) {
146690
- couldNotRecover(debt, error);
146842
+ for (const debt of debts) couldNotRecover(debt, error);
146691
146843
  }
146692
- }
146844
+ };
146845
+ const recoverTurn = (selection, turn, execution) => {
146846
+ const { debt } = selection;
146847
+ if (turn === void 0 || turn.state === "pending" || turn.state === "running" || execution.session?.activeTurnId === turn.turnId)
146848
+ return;
146849
+ if (turn.completedAt !== null && Date.parse(turn.completedAt) < Date.now() - MAX_AGE_MS) {
146850
+ core.host.log.info("chat discarded a conclusion older than seven days", { ...debt });
146851
+ ledger.discard(selection);
146852
+ return;
146853
+ }
146854
+ const binding = core.bindingForChatKey(selection.debt.chatKey);
146855
+ if (binding === void 0) return;
146856
+ enqueueConclusion(core, ledger, binding, selection, async (owed) => {
146857
+ try {
146858
+ if (turn.state === "error") {
146859
+ const detail = execution.latestTurn?.state === "error" && execution.latestTurn.turnId === turn.turnId ? execution.session?.lastError : void 0;
146860
+ return delivery.sendText(
146861
+ binding,
146862
+ debt.threadId,
146863
+ failures.forFailure(debt.threadId, detail),
146864
+ true,
146865
+ turn.turnId,
146866
+ owed
146867
+ );
146868
+ }
146869
+ const send = await delivery.prepareConclusion(debt.threadId, turn.turnId);
146870
+ return send(binding, { recovered: true, streaming: false, owed });
146871
+ } catch (error) {
146872
+ couldNotRecover(debt, error);
146873
+ return false;
146874
+ }
146875
+ });
146876
+ };
146877
+ const recoverConclusion = async (debt) => {
146878
+ await core.ready;
146879
+ await recoverThread(
146880
+ debt.threadId,
146881
+ Object.values(conclusionDebtsOf(core.state)).filter(
146882
+ (candidate) => candidate.threadId === debt.threadId
146883
+ ),
146884
+ debt.userMessageId
146885
+ );
146886
+ };
146887
+ const recoverConclusions = async () => {
146888
+ await core.ready;
146889
+ const byThread = /* @__PURE__ */ new Map();
146890
+ for (const debt of Object.values(conclusionDebtsOf(core.state))) {
146891
+ const group = byThread.get(debt.threadId) ?? [];
146892
+ group.push(debt);
146893
+ byThread.set(debt.threadId, group);
146894
+ }
146895
+ for (const [threadId, debts] of byThread) {
146896
+ if (disposed) return;
146897
+ await recoverThread(threadId, debts);
146898
+ }
146899
+ };
146900
+ return { recoverConclusion, recoverConclusions };
146693
146901
  };
146694
146902
 
146695
146903
  // src/notificationTargets.ts
@@ -146713,7 +146921,13 @@ var makeNotifications = (core, deps) => {
146713
146921
  const { takeWorking } = deps;
146714
146922
  const { turnCards } = deps.replyCards;
146715
146923
  const { failures, conclusions, delivery } = deps;
146716
- void recoverConclusions(core, conclusions, delivery, failures);
146924
+ const { recoverConclusion, recoverConclusions } = makeConclusionRecovery(
146925
+ core,
146926
+ conclusions,
146927
+ delivery,
146928
+ failures
146929
+ );
146930
+ void recoverConclusions();
146717
146931
  const projectByThread = /* @__PURE__ */ new Map();
146718
146932
  const projectOfThread = async (threadId) => {
146719
146933
  const known = projectByThread.get(threadId);
@@ -146761,14 +146975,14 @@ var makeNotifications = (core, deps) => {
146761
146975
  await ready;
146762
146976
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
146763
146977
  const owes = Object.values(conclusionDebtsOf(core.state)).some(
146764
- (debt2) => debt2.threadId === threadId
146978
+ (debt) => debt.threadId === threadId
146765
146979
  );
146766
- const { debt, handled, execution } = said || owes || targets.length > 0 ? await conclusions.forTurn(threadId) : { debt: void 0, handled: false, execution: null };
146767
- if (debt !== void 0) {
146768
- const binding = bindingForChatKey(debt.chatKey);
146980
+ const { selection, handled, execution } = said || owes || targets.length > 0 ? await conclusions.forTurn(threadId) : { selection: void 0, handled: false, execution: null };
146981
+ if (selection !== void 0) {
146982
+ const binding = bindingForChatKey(selection.debt.chatKey);
146769
146983
  if (binding !== void 0 && !streamsTo(binding, threadId) && execution?.session?.activeTurnId == null && execution?.latestTurn?.state !== "running" && execution?.latestTurn?.state !== "error" && payload.session?.status !== "error") {
146770
146984
  const turnId = execution?.latestTurn?.turnId ?? null;
146771
- enqueueConclusion(core, conclusions, binding, debt, async (owed) => {
146985
+ enqueueConclusion(core, conclusions, binding, selection, async (owed) => {
146772
146986
  const send = await delivery.prepareConclusion(threadId, turnId);
146773
146987
  return send(binding, { recovered: false, streaming: false, owed });
146774
146988
  });
@@ -146803,9 +147017,9 @@ var makeNotifications = (core, deps) => {
146803
147017
  threadsWithSomethingToReport.delete(threadId);
146804
147018
  await ready;
146805
147019
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
146806
- const { debt, handled, execution } = await conclusions.forTurn(threadId);
146807
- const debtBinding = debt === void 0 ? void 0 : bindingForChatKey(debt.chatKey);
146808
- const recipients = debt !== void 0 ? debtBinding === void 0 ? [] : [debtBinding] : targets;
147020
+ const { selection, handled, execution } = await conclusions.forTurn(threadId);
147021
+ const debtBinding = selection === void 0 ? void 0 : bindingForChatKey(selection.debt.chatKey);
147022
+ const recipients = selection !== void 0 ? debtBinding === void 0 ? [] : [debtBinding] : targets;
146809
147023
  const textTargets = handled ? [] : recipients.filter(
146810
147024
  (binding) => !(streamsTo(binding, threadId) && turnCards.covers(threadId))
146811
147025
  );
@@ -146816,7 +147030,7 @@ var makeNotifications = (core, deps) => {
146816
147030
  if (notice !== null) {
146817
147031
  for (const binding of textTargets) {
146818
147032
  const send = (owed) => delivery.sendText(binding, threadId, notice, false, null, owed);
146819
- enqueueConclusion(core, conclusions, binding, debt, send);
147033
+ enqueueConclusion(core, conclusions, binding, selection, send);
146820
147034
  }
146821
147035
  }
146822
147036
  for (const binding of targets) {
@@ -146831,7 +147045,7 @@ var makeNotifications = (core, deps) => {
146831
147045
  const rememberThreadProject = (threadId, projectId) => {
146832
147046
  projectByThread.set(threadId, projectId);
146833
147047
  };
146834
- return { rememberThreadProject };
147048
+ return { rememberThreadProject, recoverConclusion };
146835
147049
  };
146836
147050
 
146837
147051
  // src/panelCommands.ts
@@ -147806,10 +148020,10 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147806
148020
  onOpened: (card) => commit("turn-card-opened", card),
147807
148021
  onClosed: (cardId) => commit("turn-card-closed", { cardId }),
147808
148022
  finalize: async (threadId, turnId, chatKey, send) => {
147809
- const { debt, handled } = await conclusions.forTurn(threadId, turnId);
148023
+ const { selection, handled } = await conclusions.forTurn(threadId, turnId);
147810
148024
  if (handled) return true;
147811
- if (debt === void 0) return send();
147812
- if (!await conclusions.deliver(debt, send)) return false;
148025
+ if (selection === void 0) return send();
148026
+ if (!await conclusions.deliver(selection, send)) return false;
147813
148027
  const binding = bindingForChatKey(chatKey);
147814
148028
  if (binding === void 0) return true;
147815
148029
  const sendConclusion = await delivery.prepareConclusion(threadId, turnId);
@@ -147817,7 +148031,7 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147817
148031
  return true;
147818
148032
  },
147819
148033
  fallback: async (threadId, turnId, chatKey, view) => {
147820
- const { debt, handled } = await conclusions.forTurn(threadId, turnId);
148034
+ const { selection, handled } = await conclusions.forTurn(threadId, turnId);
147821
148035
  if (handled) return;
147822
148036
  const binding = bindingForChatKey(chatKey);
147823
148037
  if (binding === void 0) return;
@@ -147826,7 +148040,7 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147826
148040
  core,
147827
148041
  conclusions,
147828
148042
  binding,
147829
- debt,
148043
+ selection,
147830
148044
  (owed) => delivery.sendText(binding, threadId, text4, false, turnId, owed)
147831
148045
  );
147832
148046
  },
@@ -148211,7 +148425,7 @@ var teamGoalRequestId = (binding, messageId) => {
148211
148425
  return NodeCrypto5.createHash("sha256").update(identity2).digest("hex");
148212
148426
  };
148213
148427
  var makeTeamGoals = (core) => {
148214
- const { host, t, deliver, commit } = core;
148428
+ const { host, t, deliver, commitDurably } = core;
148215
148429
  const hasRosterFor = async (binding) => {
148216
148430
  try {
148217
148431
  const roster = await host.plugins.invoke({
@@ -148255,13 +148469,16 @@ var makeTeamGoals = (core) => {
148255
148469
  throw new Error("team startGoal returned an invalid result");
148256
148470
  }
148257
148471
  topicRoot?.confirmConsumed();
148258
- commit("team-goal-started", { chatKey: chatKeyOf(binding), threadId: result2.leadThreadId });
148259
148472
  goal = { leadThreadId: result2.leadThreadId, leadName: result2.leadName };
148260
148473
  } catch (error) {
148261
148474
  host.log.warn("chat could not start the team goal", { error: describeError(error) });
148262
148475
  await deliver(binding, t("team.startFailed"));
148263
148476
  return null;
148264
148477
  }
148478
+ await commitDurably("team-goal-started", {
148479
+ chatKey: chatKeyOf(binding),
148480
+ threadId: goal.leadThreadId
148481
+ });
148265
148482
  await accepted?.(goal.leadThreadId);
148266
148483
  await deliver(
148267
148484
  binding,
@@ -148955,6 +149172,7 @@ var makeTelegramAdapter = (deps) => {
148955
149172
  });
148956
149173
  return {
148957
149174
  platform: "telegram",
149175
+ redeliversUnacknowledged: true,
148958
149176
  botIdentity: tokenFingerprint,
148959
149177
  icon: "telegram",
148960
149178
  label: "Telegram",
@@ -149066,14 +149284,16 @@ var makeTelegramAdapter = (deps) => {
149066
149284
  }
149067
149285
  }
149068
149286
  if (!running) break;
149069
- advanceOffset(update);
149070
149287
  const senderId = message.from === void 0 ? null : String(message.from.id);
149071
149288
  const senderName2 = telegramDisplayName(message.from);
149072
149289
  const chatKind = message.chat.type === "private" ? "private" : "group";
149073
149290
  if (message.voice !== void 0 || text4 === "" && resource === null) {
149074
149291
  const unsupported = unsupportedTypeOf(message);
149075
- if (unsupported === null) continue;
149076
- handlers.onMessage({
149292
+ if (unsupported === null) {
149293
+ advanceOffset(update);
149294
+ continue;
149295
+ }
149296
+ await handlers.onMessage({
149077
149297
  messageId: String(message.message_id),
149078
149298
  chatId: chatIdOf(message),
149079
149299
  senderId,
@@ -149082,11 +149302,13 @@ var makeTelegramAdapter = (deps) => {
149082
149302
  resources: [],
149083
149303
  unsupportedType: unsupported
149084
149304
  });
149305
+ if (!running) break;
149306
+ advanceOffset(update);
149085
149307
  continue;
149086
149308
  }
149087
149309
  const parentChatId = parentChatIdOf(message);
149088
149310
  const replyTo = telegramReplyReference(message, botUsername, botId);
149089
- handlers.onMessage({
149311
+ await handlers.onMessage({
149090
149312
  messageId: String(message.message_id),
149091
149313
  chatId: chatIdOf(message),
149092
149314
  ...parentChatId === void 0 ? {} : { parentChatId },
@@ -149106,6 +149328,8 @@ var makeTelegramAdapter = (deps) => {
149106
149328
  }
149107
149329
  ]
149108
149330
  });
149331
+ if (!running) break;
149332
+ advanceOffset(update);
149109
149333
  }
149110
149334
  } catch (error) {
149111
149335
  if (!running) return;
@@ -149581,6 +149805,7 @@ var makeTurns = (core, deps) => {
149581
149805
  const startTurn = async (binding, message, accepted, inbox) => {
149582
149806
  const chatKey = chatKeyOf(binding);
149583
149807
  const text4 = message.text;
149808
+ const replayed = inbox?.replayed === true || (inbox?.entry?.attempts ?? 0) > 0;
149584
149809
  const threadId = inbox?.entry?.threadId ?? await ensureThread(binding, message);
149585
149810
  if (threadId === null) {
149586
149811
  await inbox?.complete();
@@ -149685,14 +149910,10 @@ var makeTurns = (core, deps) => {
149685
149910
  );
149686
149911
  if (!dispatched) return null;
149687
149912
  topicRoot?.confirmConsumed();
149688
- if (intentAction?.kind !== "steer") {
149689
- deps.conclusions.owe({
149690
- chatKey,
149691
- threadId,
149692
- userMessageId,
149693
- recordedAt: (/* @__PURE__ */ new Date()).toISOString()
149694
- });
149695
- }
149913
+ await deps.conclusions.owe(
149914
+ { chatKey, threadId, userMessageId, recordedAt: (/* @__PURE__ */ new Date()).toISOString() },
149915
+ replayed ? deps.recoverConclusion : void 0
149916
+ );
149696
149917
  } finally {
149697
149918
  registered();
149698
149919
  }
@@ -151127,7 +151348,7 @@ ${kept}`;
151127
151348
  if (message.chatType !== "single") return;
151128
151349
  client.rememberRequest(message.chatId, message.requestId);
151129
151350
  client.rememberConversation(message.chatId, message.conversation);
151130
- handlers.onMessage({
151351
+ void handlers.onMessage({
151131
151352
  // 不保存短效 req_id:重启后的回话走新连接的 aibot_send_msg 主动发送,
151132
151353
  // 不冒充原消息的回答,也不复用旧连接的 rememberRequest / 流状态。
151133
151354
  messageId: message.messageId,
@@ -151144,6 +151365,10 @@ ${kept}`;
151144
151365
  mimeType: resource.mimeType,
151145
151366
  kind: resource.kind
151146
151367
  }))
151368
+ }).catch((error) => {
151369
+ deps.log.warn("wecom could not persist inbound message", {
151370
+ error: describeError(error)
151371
+ });
151147
151372
  });
151148
151373
  }
151149
151374
  }
@@ -152239,12 +152464,19 @@ var makeWecomAppAdapter = (deps) => {
152239
152464
  }
152240
152465
  const messageId = message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`;
152241
152466
  const base2 = { messageId, chatId, senderId: chatId };
152467
+ const receive = (inbound) => {
152468
+ void handlers.onMessage(inbound).catch((error) => {
152469
+ deps.log.warn("wecom-app could not persist inbound message", {
152470
+ error: describeError(error)
152471
+ });
152472
+ });
152473
+ };
152242
152474
  if (message.msgtype === "text") {
152243
- handlers.onMessage({ ...base2, text: message.text?.content?.trim() ?? "", resources: [] });
152475
+ receive({ ...base2, text: message.text?.content?.trim() ?? "", resources: [] });
152244
152476
  return;
152245
152477
  }
152246
152478
  if (message.msgtype === "image" && message.mediaId !== void 0 && message.mediaId !== "") {
152247
- handlers.onMessage({
152479
+ receive({
152248
152480
  ...base2,
152249
152481
  text: "",
152250
152482
  resources: [
@@ -152253,7 +152485,7 @@ var makeWecomAppAdapter = (deps) => {
152253
152485
  });
152254
152486
  return;
152255
152487
  }
152256
- handlers.onMessage({
152488
+ receive({
152257
152489
  ...base2,
152258
152490
  text: "",
152259
152491
  resources: [],
@@ -152463,7 +152695,7 @@ var makeWecomAppAdapter = (deps) => {
152463
152695
  // src/index.ts
152464
152696
  var definition = {
152465
152697
  id: "im",
152466
- apiVersion: 21,
152698
+ apiVersion: 23,
152467
152699
  // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
152468
152700
  activate: async (ctx, testing = {}) => {
152469
152701
  const { dispatchSleep } = testing;
@@ -152592,6 +152824,7 @@ var definition = {
152592
152824
  const turns = makeTurns(core, {
152593
152825
  conclusions,
152594
152826
  attachments: inboundAttachments,
152827
+ recoverConclusion: notifications.recoverConclusion,
152595
152828
  queueFlow: interactions.queueFlow,
152596
152829
  rememberThreadProject: notifications.rememberThreadProject,
152597
152830
  noteInboundUserMessage: webSync.noteInboundUserMessage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/plugin-im",
3
- "version": "0.2.3214",
3
+ "version": "0.2.3220",
4
4
  "description": "Talk to your agents from Telegram, WeCom, Lark, or WeChat (experimental).",
5
5
  "keywords": [
6
6
  "im",
@@ -32,7 +32,8 @@
32
32
  "markdown-it": "^15.0.2",
33
33
  "react": "19.2.6",
34
34
  "vite-plus": "0.2.2",
35
- "@ycodium-ai/plugin-api": "0.2.3214"
35
+ "ycodium-react-test-harness": "0.0.0",
36
+ "@ycodium-ai/plugin-api": "0.2.3220"
36
37
  },
37
38
  "ycodium": {
38
39
  "plugin": "./dist/index.js",