@ycodium-ai/plugin-im 0.2.3244 → 0.2.3250

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
@@ -126162,16 +126162,152 @@ import { definePlugin } from "@ycodium-ai/plugin-api";
126162
126162
  // src/errors.ts
126163
126163
  var describeError = (error) => error instanceof Error ? error.message : String(error);
126164
126164
 
126165
+ // src/inboundOutcome.ts
126166
+ var runInbound = async (core, binding, messageId, execute, lifecycle) => {
126167
+ let outcome;
126168
+ let failure2;
126169
+ let accepted = false;
126170
+ let completed = false;
126171
+ const completeFailure = async () => {
126172
+ try {
126173
+ await lifecycle?.complete();
126174
+ completed = true;
126175
+ } catch (error) {
126176
+ core.host.log.warn("chat could not complete a failed inbound message", {
126177
+ messageId,
126178
+ error: describeError(error)
126179
+ });
126180
+ }
126181
+ };
126182
+ const reportFailure = async () => {
126183
+ try {
126184
+ if (!await core.deliver(
126185
+ binding,
126186
+ core.t(completed ? "reply.inboundFailed" : "reply.inboundDeferred")
126187
+ ))
126188
+ core.host.log.warn("chat could not deliver an inbound failure notice", { messageId });
126189
+ } catch (error) {
126190
+ core.host.log.warn("chat could not deliver an inbound failure notice", {
126191
+ messageId,
126192
+ error: describeError(error)
126193
+ });
126194
+ }
126195
+ };
126196
+ try {
126197
+ outcome = await execute(() => {
126198
+ accepted = true;
126199
+ lifecycle?.clearDeferred();
126200
+ });
126201
+ } catch (error) {
126202
+ failure2 = { error };
126203
+ if (!accepted) await completeFailure();
126204
+ } finally {
126205
+ if (!accepted) {
126206
+ if (outcome?.status === "deferred") lifecycle?.rememberDeferred();
126207
+ else lifecycle?.clearDeferred();
126208
+ }
126209
+ if (!accepted && (outcome === void 0 || outcome.status === "settled") || outcome?.status === "started" && outcome.claimsPlaceholder === false) {
126210
+ try {
126211
+ await core.adapterFor(binding.platform)?.inboundSettled?.(binding, messageId);
126212
+ } catch (error) {
126213
+ core.host.log.warn("chat could not settle inbound message", {
126214
+ messageId,
126215
+ error: describeError(error)
126216
+ });
126217
+ }
126218
+ }
126219
+ }
126220
+ if (failure2 !== void 0) {
126221
+ if (!accepted) await reportFailure();
126222
+ throw failure2.error;
126223
+ }
126224
+ if (outcome?.status === "deferred") outcome.resume?.();
126225
+ if (outcome?.status === "settled") {
126226
+ try {
126227
+ await outcome.respond?.();
126228
+ } catch (error) {
126229
+ core.host.log.warn("chat could not deliver an inbound response", {
126230
+ messageId,
126231
+ error: describeError(error)
126232
+ });
126233
+ throw error;
126234
+ }
126235
+ }
126236
+ };
126237
+
126165
126238
  // src/acknowledgements.ts
126166
- var makeAcknowledgements = (core) => {
126239
+ var TYPING_KEEPALIVE_MS = 4e3;
126240
+ var makeAcknowledgements = (core, intervals) => {
126167
126241
  const { host, adapterFor } = core;
126168
126242
  const acknowledgements = /* @__PURE__ */ new Map();
126243
+ const deferred = /* @__PURE__ */ new Map();
126244
+ const typingKeepalive = /* @__PURE__ */ new Map();
126245
+ const messageKey = (identity2) => JSON.stringify([identity2.platform, identity2.rowId, identity2.chatId, identity2.messageId]);
126246
+ const noteTypingFailure = (error) => {
126247
+ host.log.info("chat could not show typing", { error: describeError(error) });
126248
+ };
126249
+ const clearDeferred = (identity2) => {
126250
+ deferred.delete(messageKey(identity2));
126251
+ };
126252
+ const stopTyping = (threadId) => {
126253
+ const lane = typingKeepalive.get(threadId);
126254
+ if (lane === void 0) return;
126255
+ typingKeepalive.delete(threadId);
126256
+ lane.cancel();
126257
+ };
126258
+ const stopTypingFor = (threadId) => {
126259
+ stopTyping(threadId);
126260
+ };
126261
+ const showTyping = (binding, threadId, sendTyping) => {
126262
+ const existing = typingKeepalive.get(threadId);
126263
+ if (existing !== void 0) {
126264
+ existing.retarget(binding, sendTyping);
126265
+ existing.send();
126266
+ return;
126267
+ }
126268
+ let pending = false;
126269
+ let stopped = false;
126270
+ let currentBinding = binding;
126271
+ let currentSend = sendTyping;
126272
+ const send = () => {
126273
+ if (stopped || pending) return;
126274
+ pending = true;
126275
+ let sending;
126276
+ try {
126277
+ sending = currentSend(currentBinding);
126278
+ } catch (error) {
126279
+ pending = false;
126280
+ noteTypingFailure(error);
126281
+ return;
126282
+ }
126283
+ void sending.catch(noteTypingFailure).finally(() => {
126284
+ pending = false;
126285
+ });
126286
+ };
126287
+ const dispose = intervals({
126288
+ name: `typing-keepalive:${threadId}`,
126289
+ everyMs: TYPING_KEEPALIVE_MS,
126290
+ handle: () => {
126291
+ send();
126292
+ }
126293
+ });
126294
+ typingKeepalive.set(threadId, {
126295
+ send,
126296
+ cancel: () => {
126297
+ stopped = true;
126298
+ dispose();
126299
+ },
126300
+ retarget: (nextBinding, nextSend) => {
126301
+ currentBinding = nextBinding;
126302
+ currentSend = nextSend;
126303
+ }
126304
+ });
126305
+ send();
126306
+ };
126169
126307
  const markWorking = async (binding, threadId, messageId) => {
126170
126308
  const adapter = adapterFor(binding.platform);
126171
126309
  if (adapter?.sendTyping !== void 0) {
126172
- void adapter.sendTyping(binding).catch((error) => {
126173
- host.log.info("chat could not show typing", { error: describeError(error) });
126174
- });
126310
+ showTyping(binding, threadId, adapter.sendTyping.bind(adapter));
126175
126311
  }
126176
126312
  if (adapter?.acknowledge === void 0) return;
126177
126313
  try {
@@ -126186,6 +126322,7 @@ var makeAcknowledgements = (core) => {
126186
126322
  }
126187
126323
  };
126188
126324
  const takeWorking = (binding, threadId, outcome) => {
126325
+ stopTyping(threadId);
126189
126326
  const adapter = adapterFor(binding.platform);
126190
126327
  const clear = adapter?.clearAcknowledge?.bind(adapter);
126191
126328
  const pending = acknowledgements.get(threadId);
@@ -126204,7 +126341,26 @@ var makeAcknowledgements = (core) => {
126204
126341
  };
126205
126342
  };
126206
126343
  const clearWorking = (binding, threadId, outcome) => takeWorking(binding, threadId, outcome)();
126207
- return { markWorking, takeWorking, clearWorking };
126344
+ const rememberDeferred = (identity2, binding) => {
126345
+ const key2 = messageKey(identity2);
126346
+ if (!deferred.has(key2)) deferred.set(key2, { binding, identity: identity2 });
126347
+ };
126348
+ const stopDeferred = async (rowId) => {
126349
+ for (const [key2, { binding, identity: identity2 }] of [...deferred]) {
126350
+ if (rowId !== void 0 && identity2.rowId !== rowId) continue;
126351
+ if (!deferred.delete(key2)) continue;
126352
+ await runInbound(core, binding, identity2.messageId, async () => ({ status: "settled" }));
126353
+ }
126354
+ };
126355
+ return {
126356
+ markWorking,
126357
+ takeWorking,
126358
+ clearWorking,
126359
+ clearDeferred,
126360
+ rememberDeferred,
126361
+ stopDeferred,
126362
+ stopTypingFor
126363
+ };
126208
126364
  };
126209
126365
 
126210
126366
  // src/botConfig.ts
@@ -128085,7 +128241,8 @@ var makeChatSetup = (core, deps) => {
128085
128241
  await deliver(binding, t("repo.done", { title: target2.title }));
128086
128242
  const pending = pendingMessages.get(chatKey);
128087
128243
  pendingMessages.forget((key2) => key2 === chatKey);
128088
- if (pending !== void 0) await deps.resumeMessage(binding, pending);
128244
+ if (pending !== void 0)
128245
+ deps.enqueueForChat(chatKey, () => deps.resumeMessage(binding, pending), true);
128089
128246
  };
128090
128247
  const handleRepo = async (binding, arg) => {
128091
128248
  if (arg === "") {
@@ -128139,7 +128296,7 @@ var makeChatSetup = (core, deps) => {
128139
128296
  const only = rows.length === 1 ? rows[0] : void 0;
128140
128297
  return only === void 0 ? { kind: "list" } : { kind: "auto", projectId: only.projectId };
128141
128298
  };
128142
- const executeProjectDecision = async (binding, message, decision) => {
128299
+ const executeProjectDecision = async (binding, message, decision, deferReply) => {
128143
128300
  if (decision.kind === "loadFailed" || decision.kind === "notFound") {
128144
128301
  await deliver(
128145
128302
  binding,
@@ -128148,8 +128305,18 @@ var makeChatSetup = (core, deps) => {
128148
128305
  return false;
128149
128306
  }
128150
128307
  const chatKey = chatKeyOf(binding);
128151
- const rows = await loadProjectRows(binding);
128152
- if (rows === null) return false;
128308
+ const send = async (text3) => {
128309
+ const reply = async () => {
128310
+ await deliver(binding, text3);
128311
+ };
128312
+ if (deferReply === void 0) await reply();
128313
+ else deferReply(reply);
128314
+ };
128315
+ const rows = await readProjectRows(chatKey);
128316
+ if (rows === null) {
128317
+ await send(t("panel.loadFailed"));
128318
+ return false;
128319
+ }
128153
128320
  if (decision.kind === "pick") {
128154
128321
  await selectProject(binding, decision.projectId, rows);
128155
128322
  return false;
@@ -128157,12 +128324,12 @@ var makeChatSetup = (core, deps) => {
128157
128324
  if (decision.kind === "auto") {
128158
128325
  const target2 = rows.find((row) => row.projectId === decision.projectId);
128159
128326
  if (target2 === void 0) {
128160
- await deliver(binding, t("repo.gone"));
128327
+ await send(t("repo.gone"));
128161
128328
  return false;
128162
128329
  }
128163
128330
  commit("chat-updated", { chatKey, projectId: target2.projectId });
128164
128331
  pendingMessages.forget((key2) => key2 === chatKey);
128165
- await deliver(binding, t("repo.autoSelected", { title: target2.title }));
128332
+ await send(t("repo.autoSelected", { title: target2.title }));
128166
128333
  return true;
128167
128334
  }
128168
128335
  pendingMessages.set(chatKey, message);
@@ -133373,6 +133540,7 @@ var Uint8Array2 = /* @__PURE__ */ instanceOf(globalThis.Uint8Array, {
133373
133540
  });
133374
133541
 
133375
133542
  // src/chatCommand.ts
133543
+ var HISTORY_COUNTS = { defaultCount: 3, maxCount: 5 };
133376
133544
  var SimpleChatCommand = Struct({
133377
133545
  kind: Literals([
133378
133546
  "stop",
@@ -133390,6 +133558,7 @@ var ParsedChatCommand = Union2([
133390
133558
  SimpleChatCommand,
133391
133559
  Struct({ kind: Literal2("sessions"), arg: optionalKey2(String4) }),
133392
133560
  Struct({ kind: Literals(["resume", "model", "repo"]), arg: String4 }),
133561
+ Struct({ kind: Literal2("history"), count: NullOr(Number5) }),
133393
133562
  Struct({
133394
133563
  kind: Literal2("preset"),
133395
133564
  arg: String4,
@@ -133441,6 +133610,16 @@ var parseChatCommand = (input) => {
133441
133610
  if (sessions !== void 0) {
133442
133611
  return sessions === "" ? { kind: "sessions" } : { kind: "sessions", arg: sessions };
133443
133612
  }
133613
+ const history = commandArgument(
133614
+ trimmed,
133615
+ input.chinese,
133616
+ /^\/history(?:\s+([\s\S]*))?$/i,
133617
+ /^会话历史(?:\s+([\s\S]*))?$/
133618
+ );
133619
+ if (history !== void 0) {
133620
+ const count = history === "" ? HISTORY_COUNTS.defaultCount : /^\d+$/.test(history) ? Math.min(Number(history), HISTORY_COUNTS.maxCount) : 0;
133621
+ return { kind: "history", count: count > 0 ? count : null };
133622
+ }
133444
133623
  const resume = resumeArgument(input.text, input.chinese);
133445
133624
  if (resume !== void 0) {
133446
133625
  return resume === "" ? { kind: "sessions" } : { kind: "resume", arg: resume };
@@ -133480,6 +133659,7 @@ var helpText = (t, options = {}) => [
133480
133659
  t("help.new"),
133481
133660
  t("help.sessions"),
133482
133661
  t("help.resume"),
133662
+ t("help.history", HISTORY_COUNTS),
133483
133663
  t("help.syncNote"),
133484
133664
  t("help.model"),
133485
133665
  t("help.preset"),
@@ -133492,6 +133672,175 @@ var helpText = (t, options = {}) => [
133492
133672
  ...options.panel === true ? ["", t("help.panel")] : []
133493
133673
  ].join("\n");
133494
133674
 
133675
+ // src/deadline.ts
133676
+ var withDeadline = async (timeoutMs, load) => {
133677
+ const signal = AbortSignal.timeout(timeoutMs);
133678
+ let onAbort = () => {
133679
+ };
133680
+ const aborted = new Promise((_, reject) => {
133681
+ onAbort = () => reject(signal.reason);
133682
+ signal.addEventListener("abort", onAbort, { once: true });
133683
+ });
133684
+ try {
133685
+ return await Promise.race([load(signal), aborted]);
133686
+ } finally {
133687
+ signal.removeEventListener("abort", onAbort);
133688
+ }
133689
+ };
133690
+
133691
+ // src/threadMessages.ts
133692
+ import {
133693
+ isThreadNotFound,
133694
+ threadMessagesNewestFirst
133695
+ } from "@ycodium-ai/plugin-api";
133696
+ var PAGE_SIZE = 20;
133697
+ async function* newestFirst(threads, threadId) {
133698
+ try {
133699
+ yield* threadMessagesNewestFirst(threads, threadId, { pageSize: PAGE_SIZE });
133700
+ } catch (error) {
133701
+ if (!isThreadNotFound(error)) throw error;
133702
+ }
133703
+ }
133704
+ async function attachmentsOf(threads, threadId, message) {
133705
+ const collected = [...message.attachments ?? []];
133706
+ const total = message.attachmentsTotal ?? collected.length;
133707
+ while (collected.length < total) {
133708
+ let page;
133709
+ try {
133710
+ page = await threads.messages.list({
133711
+ threadId,
133712
+ messageId: message.id,
133713
+ attachmentOffset: collected.length,
133714
+ textLimit: 2
133715
+ });
133716
+ } catch (error) {
133717
+ if (isThreadNotFound(error)) break;
133718
+ throw error;
133719
+ }
133720
+ const more = page.items[0]?.attachments ?? [];
133721
+ if (more.length === 0) break;
133722
+ collected.push(...more);
133723
+ }
133724
+ return collected;
133725
+ }
133726
+ async function latestMessage(threads, threadId, accept) {
133727
+ for await (const message of newestFirst(threads, threadId)) {
133728
+ if (accept(message)) return message;
133729
+ }
133730
+ return void 0;
133731
+ }
133732
+ async function recentMessages(threads, threadId, count, accept) {
133733
+ const collected = [];
133734
+ for await (const message of newestFirst(threads, threadId)) {
133735
+ if (!accept(message)) continue;
133736
+ collected.push(message);
133737
+ if (collected.length >= count) break;
133738
+ }
133739
+ return collected.toReversed();
133740
+ }
133741
+ async function messagesAfterLastUser(threads, threadId) {
133742
+ const after = [];
133743
+ for await (const message of newestFirst(threads, threadId)) {
133744
+ if (message.role === "user") break;
133745
+ after.push(message);
133746
+ }
133747
+ return after.toReversed();
133748
+ }
133749
+
133750
+ // src/historyCommand.ts
133751
+ var READ_TIMEOUT_MS = 1e4;
133752
+ var MAX_PAGES = 3;
133753
+ var TEXT_LIMIT = 1e3;
133754
+ var makeHistoryCommand = (core) => {
133755
+ const { host, t, deliver } = core;
133756
+ return async (binding, command, chatKind) => {
133757
+ if (chatKind !== "private") {
133758
+ await deliver(binding, t("history.directOnly"));
133759
+ return;
133760
+ }
133761
+ const count = command.count;
133762
+ if (count === null) {
133763
+ await deliver(binding, t("history.usage", HISTORY_COUNTS));
133764
+ return;
133765
+ }
133766
+ const chatKey = chatKeyOf(binding);
133767
+ const threadId = chatOf(core.state, chatKey).threadId;
133768
+ if (threadId === null) {
133769
+ await deliver(binding, t("history.noSession"));
133770
+ return;
133771
+ }
133772
+ const switched = () => {
133773
+ if (chatOf(core.state, chatKey).threadId === threadId) return false;
133774
+ host.log.info("chat cancelled a history read after the session changed", { threadId });
133775
+ return true;
133776
+ };
133777
+ const adapter = core.adapterFor(binding.platform);
133778
+ if (adapter?.sendTyping !== void 0) {
133779
+ void adapter.sendTyping(binding).catch(() => {
133780
+ host.log.info("chat could not show history typing", { threadId });
133781
+ });
133782
+ }
133783
+ let text3;
133784
+ try {
133785
+ text3 = await withDeadline(READ_TIMEOUT_MS, async (signal) => {
133786
+ const threads = {
133787
+ messages: {
133788
+ list: async (input) => {
133789
+ signal.throwIfAborted();
133790
+ const page = await host.threads.messages.list({
133791
+ ...input,
133792
+ textLimit: input.textLimit ?? TEXT_LIMIT
133793
+ });
133794
+ signal.throwIfAborted();
133795
+ return page;
133796
+ }
133797
+ }
133798
+ };
133799
+ let pages = 0;
133800
+ let reachedEnd = false;
133801
+ const bounded = {
133802
+ messages: {
133803
+ list: async (input) => {
133804
+ const page = await threads.messages.list(input);
133805
+ pages += 1;
133806
+ reachedEnd = page.nextCursor === null;
133807
+ return { ...page, nextCursor: pages >= MAX_PAGES ? null : page.nextCursor };
133808
+ }
133809
+ }
133810
+ };
133811
+ const messages = await recentMessages(
133812
+ bounded,
133813
+ threadId,
133814
+ count,
133815
+ (message) => message.role === "user" || message.role === "assistant" && !message.streaming
133816
+ );
133817
+ if (messages.length === 0) return t(reachedEnd ? "history.empty" : "history.noneRecent");
133818
+ const entries = [];
133819
+ for (const message of messages) {
133820
+ const attachments = await attachmentsOf(threads, threadId, message);
133821
+ const body = [
133822
+ message.text.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "").trim(),
133823
+ ...message.textWindow?.nextOffset != null ? [t("history.truncated")] : [],
133824
+ ...attachments.map(
133825
+ (attachment) => t(attachment.type === "image" ? "history.image" : "history.file")
133826
+ )
133827
+ ].filter(Boolean).join("\n") || t("history.noText");
133828
+ const role = t(message.role === "user" ? "history.user" : "history.assistant");
133829
+ entries.push(`${entries.length + 1}. ${role}
133830
+ ${body}`);
133831
+ }
133832
+ return [t("history.title", { count: messages.length }), ...entries].join("\n\n");
133833
+ });
133834
+ } catch {
133835
+ if (switched()) return;
133836
+ host.log.warn("chat could not read history", { threadId });
133837
+ text3 = t("history.failed");
133838
+ }
133839
+ if (switched()) return;
133840
+ await deliver(binding, text3);
133841
+ };
133842
+ };
133843
+
133495
133844
  // src/commands.ts
133496
133845
  var NEEDS_PROJECT_COMMANDS = /* @__PURE__ */ new Set(["sessions", "resume", "selectTeam", "selectThread"]);
133497
133846
  var makeCommands = (core, deps) => {
@@ -133501,8 +133850,9 @@ var makeCommands = (core, deps) => {
133501
133850
  const { showSessions, resumeSession } = deps.sessions;
133502
133851
  const { handleModel, handlePlan } = deps.modelPlan;
133503
133852
  const { openPanel } = deps;
133853
+ const handleHistory = makeHistoryCommand(core);
133504
133854
  const targetOf = (binding) => chatOf(core.state, chatKeyOf(binding)).target;
133505
- const executeCommand = async (binding, command, senderId = null) => {
133855
+ const executeCommand = async (binding, command, senderId = null, chatKind = void 0) => {
133506
133856
  const chatKey = chatKeyOf(binding);
133507
133857
  host.log.info("chat handled a command", {
133508
133858
  bot: binding.displayName,
@@ -133553,6 +133903,9 @@ var makeCommands = (core, deps) => {
133553
133903
  case "sessions":
133554
133904
  await showSessions(binding, command.arg);
133555
133905
  return;
133906
+ case "history":
133907
+ await handleHistory(binding, command, chatKind);
133908
+ return;
133556
133909
  case "resume":
133557
133910
  await resumeSession(binding, command.arg);
133558
133911
  return;
@@ -135185,56 +135538,6 @@ var deliveryFailure = (core, binding, threadId, turnId) => async (error) => {
135185
135538
  }
135186
135539
  };
135187
135540
 
135188
- // src/threadMessages.ts
135189
- import {
135190
- isThreadNotFound,
135191
- threadMessagesNewestFirst
135192
- } from "@ycodium-ai/plugin-api";
135193
- var PAGE_SIZE = 20;
135194
- async function* newestFirst(threads, threadId) {
135195
- try {
135196
- yield* threadMessagesNewestFirst(threads, threadId, { pageSize: PAGE_SIZE });
135197
- } catch (error) {
135198
- if (!isThreadNotFound(error)) throw error;
135199
- }
135200
- }
135201
- async function attachmentsOf(threads, threadId, message) {
135202
- const collected = [...message.attachments ?? []];
135203
- const total = message.attachmentsTotal ?? collected.length;
135204
- while (collected.length < total) {
135205
- let page;
135206
- try {
135207
- page = await threads.messages.list({
135208
- threadId,
135209
- messageId: message.id,
135210
- attachmentOffset: collected.length,
135211
- textLimit: 2
135212
- });
135213
- } catch (error) {
135214
- if (isThreadNotFound(error)) break;
135215
- throw error;
135216
- }
135217
- const more = page.items[0]?.attachments ?? [];
135218
- if (more.length === 0) break;
135219
- collected.push(...more);
135220
- }
135221
- return collected;
135222
- }
135223
- async function latestMessage(threads, threadId, accept) {
135224
- for await (const message of newestFirst(threads, threadId)) {
135225
- if (accept(message)) return message;
135226
- }
135227
- return void 0;
135228
- }
135229
- async function messagesAfterLastUser(threads, threadId) {
135230
- const after = [];
135231
- for await (const message of newestFirst(threads, threadId)) {
135232
- if (message.role === "user") break;
135233
- after.push(message);
135234
- }
135235
- return after.toReversed();
135236
- }
135237
-
135238
135541
  // src/conclusionDelivery.ts
135239
135542
  var ALWAYS_OWED = () => true;
135240
135543
  var makeConclusionDelivery = (core, anchors) => {
@@ -135604,16 +135907,6 @@ var workForMessage = (message) => ({
135604
135907
  });
135605
135908
 
135606
135909
  // src/inboundRoutes.ts
135607
- var settleInbound = async (core, binding, messageId) => {
135608
- try {
135609
- await core.adapterFor(binding.platform)?.inboundSettled?.(binding, messageId);
135610
- } catch (error) {
135611
- core.host.log.warn("chat could not settle inbound message", {
135612
- messageId,
135613
- error: describeError(error)
135614
- });
135615
- }
135616
- };
135617
135910
  var executeInboundRoute = async (core, actions, input) => {
135618
135911
  const { current, message, route, work } = input;
135619
135912
  const target2 = input.target;
@@ -135623,82 +135916,113 @@ var executeInboundRoute = async (core, actions, input) => {
135623
135916
  switch (route.kind) {
135624
135917
  case "command":
135625
135918
  await complete();
135626
- await actions.commands.executeCommand(target2, route.command, message.senderId);
135627
- await settleInbound(core, target2, message.messageId);
135628
- return;
135629
- case "project":
135630
- if (route.decision.kind !== "auto") await complete();
135631
- if (await actions.chatSetup.executeProjectDecision(target2, message, route.decision)) {
135632
- await actions.continueMessage({
135919
+ await actions.commands.executeCommand(
135920
+ target2,
135921
+ route.command,
135922
+ message.senderId,
135923
+ message.chatKind
135924
+ );
135925
+ return { status: "settled" };
135926
+ case "project": {
135927
+ if (route.decision.kind !== "auto") {
135928
+ await complete();
135929
+ await actions.chatSetup.executeProjectDecision(target2, message, route.decision);
135930
+ return { status: "settled" };
135931
+ }
135932
+ const replies = [];
135933
+ const respond = async () => {
135934
+ for (const reply of replies) await reply();
135935
+ };
135936
+ if (await actions.chatSetup.executeProjectDecision(
135937
+ target2,
135938
+ message,
135939
+ route.decision,
135940
+ (reply) => replies.push(reply)
135941
+ )) {
135942
+ const outcome = await actions.continueMessage({
135633
135943
  ...core.bindingOf(current, message.chatId),
135634
135944
  reply: target2.reply
135635
135945
  });
135636
- } else {
135637
- if (route.decision.kind === "auto") await complete();
135638
- await settleInbound(core, target2, message.messageId);
135946
+ if (outcome.status === "settled")
135947
+ return {
135948
+ status: "settled",
135949
+ respond: async () => {
135950
+ await respond();
135951
+ await outcome.respond?.();
135952
+ }
135953
+ };
135954
+ await respond();
135955
+ return outcome;
135639
135956
  }
135640
- return;
135957
+ await complete();
135958
+ return { status: "settled", respond };
135959
+ }
135641
135960
  case "question":
135642
135961
  await complete();
135643
135962
  await actions.interactions.questionFlow.answerText(route.answer);
135644
- await settleInbound(core, target2, message.messageId);
135645
- return;
135963
+ return { status: "settled" };
135646
135964
  case "approval":
135647
135965
  await complete();
135648
135966
  await actions.approvals.answerByToken(route.token);
135649
- await settleInbound(core, target2, message.messageId);
135650
- return;
135967
+ return { status: "settled" };
135651
135968
  case "unavailable":
135652
135969
  await complete();
135653
- await settleInbound(core, target2, message.messageId);
135654
- return;
135655
- case "attachments":
135656
- await actions.turns.receiveAttachmentsOnly(target2, message, complete);
135970
+ return { status: "settled" };
135971
+ case "attachments": {
135972
+ const outcome = await actions.turns.receiveAttachmentsOnly(target2, message, complete);
135657
135973
  await complete();
135658
- await settleInbound(core, target2, message.messageId);
135659
- return;
135974
+ return outcome;
135975
+ }
135660
135976
  case "team": {
135661
135977
  const routed = { ...target2, chatId: route.chatId, projectId: route.projectId };
135662
- if (message.resources.length > 0)
135663
- await core.deliver(routed, core.t("reply.attachmentsNoTeam"));
135664
- const leadThreadId = await actions.teams.startTeamGoal(
135978
+ const outcome = await actions.teams.startTeamGoal(
135665
135979
  routed,
135666
135980
  message,
135667
- actions.acceptedWork(routed, message, work)
135981
+ actions.acceptedWork(routed, message, work, input.onAccepted)
135668
135982
  );
135669
- if (leadThreadId === null) {
135983
+ if (outcome.status === "settled") {
135670
135984
  await complete();
135671
- await settleInbound(core, target2, message.messageId);
135985
+ return {
135986
+ status: "settled",
135987
+ respond: async () => {
135988
+ if (message.resources.length > 0)
135989
+ await core.deliver(routed, core.t("reply.attachmentsNoTeam"));
135990
+ await outcome.respond?.();
135991
+ }
135992
+ };
135672
135993
  }
135673
- return;
135994
+ if (message.resources.length > 0)
135995
+ await core.deliver(routed, core.t("reply.attachmentsNoTeam"));
135996
+ return outcome;
135674
135997
  }
135675
135998
  case "turn":
135676
- await actions.openTurn(
135999
+ return actions.openTurn(
135677
136000
  { ...target2, chatId: route.chatId, projectId: route.projectId },
135678
136001
  message,
135679
- work
136002
+ work,
136003
+ input.onAccepted
135680
136004
  );
135681
- return;
135682
136005
  case "handoff": {
135683
- const executeHandoff = async () => {
136006
+ const executeHandoff = async (onAccepted) => {
135684
136007
  actions.inheritFromParent(current, route.chatId, message.chatId);
135685
136008
  const child = {
135686
136009
  ...core.bindingOf(current, route.chatId),
135687
136010
  projectId: route.projectId,
135688
136011
  reply: target2.reply
135689
136012
  };
135690
- await actions.openTurn(child, message, work);
136013
+ return actions.openTurn(child, message, work, onAccepted);
135691
136014
  };
135692
136015
  if (input.queueChatId === route.chatId) {
135693
- await executeHandoff();
135694
- return;
136016
+ return executeHandoff(input.onAccepted);
135695
136017
  }
135696
- actions.enqueueForChat(
135697
- chatKeyOf({ rowId: current.rowId, chatId: route.chatId }),
135698
- executeHandoff,
135699
- true
135700
- );
135701
- return;
136018
+ return {
136019
+ status: "deferred",
136020
+ resume: () => actions.enqueueForChat(
136021
+ chatKeyOf({ rowId: current.rowId, chatId: route.chatId }),
136022
+ () => actions.run(executeHandoff),
136023
+ true
136024
+ )
136025
+ };
135702
136026
  }
135703
136027
  }
135704
136028
  };
@@ -135875,16 +136199,14 @@ var makeInbound = (core, deps) => {
135875
136199
  }));
135876
136200
  return true;
135877
136201
  };
135878
- const turnAwayStranger = (target2, sender) => {
136202
+ const turnAwayStranger = async (target2, sender) => {
135879
136203
  host.log.warn("chat ignored a message from someone not on the allow list", {
135880
136204
  bot: target2.displayName,
135881
136205
  sender
135882
136206
  });
135883
136207
  if (sender === null) return;
135884
136208
  if (!notAllowedReplyCooldown.tryAcquire(`${target2.rowId}:${sender}`)) return;
135885
- enqueueForChat(chatKeyOf(target2), async () => {
135886
- await deliver(target2, t("reply.notAllowedYet", { id: sender }));
135887
- });
136209
+ await deliver(target2, t("reply.notAllowedYet", { id: sender }));
135888
136210
  };
135889
136211
  const currentBot = (rowId) => bots().find((candidate) => candidate.rowId === rowId);
135890
136212
  const inheritFromParent = (bot, chatId, parentChatId) => {
@@ -135911,24 +136233,24 @@ var makeInbound = (core, deps) => {
135911
136233
  if (known === void 0) noteChatAlias(bot, message.chatAlias, message.chatId);
135912
136234
  return known === void 0 || known === message.chatId ? message : { ...message, chatId: known };
135913
136235
  };
135914
- const acceptedWork = (speaker, message, work) => async (threadId, register) => {
135915
- await markWorking(speaker, threadId, message.messageId);
135916
- await register?.();
135917
- if (work !== void 0) await inboundState.complete(speaker.rowId, work.key);
135918
- };
135919
- const openTurn = async (speaker, message, work) => {
135920
- await startTurn(
135921
- speaker,
135922
- message,
135923
- acceptedWork(speaker, message, work),
135924
- work === void 0 ? void 0 : processingFor(inboundState, speaker.rowId, work, () => {
135925
- replay.enqueue(speaker.rowId, work.key, Promise.resolve(true));
135926
- })
135927
- );
135928
- };
136236
+ const acceptedWork = (speaker, message, work, onAccepted) => Object.assign(
136237
+ async (threadId, register) => {
136238
+ await markWorking(speaker, threadId, message.messageId);
136239
+ await register?.();
136240
+ if (work !== void 0) await inboundState.complete(speaker.rowId, work.key);
136241
+ },
136242
+ { onAccepted }
136243
+ );
136244
+ const openTurn = (speaker, message, work, onAccepted) => startTurn(
136245
+ speaker,
136246
+ message,
136247
+ acceptedWork(speaker, message, work, onAccepted),
136248
+ work === void 0 ? void 0 : processingFor(inboundState, speaker.rowId, work, () => {
136249
+ replay.enqueue(speaker.rowId, work.key, Promise.resolve(true));
136250
+ })
136251
+ );
135929
136252
  const resumeMessage = async (current, message, work, queueChatId = message.chatId) => {
135930
- inheritFromParent(current, message.chatId, message.parentChatId);
135931
- const target2 = {
136253
+ let target2 = {
135932
136254
  ...bindingOf2(current, message.chatId),
135933
136255
  reply: anchorOfMessage(adapterFor(current.platform), message)
135934
136256
  };
@@ -135939,14 +136261,24 @@ var makeInbound = (core, deps) => {
135939
136261
  const complete = async () => {
135940
136262
  if (work !== void 0) await inboundState.complete(current.rowId, work.key);
135941
136263
  };
136264
+ const identity2 = {
136265
+ platform: current.platform,
136266
+ rowId: current.rowId,
136267
+ chatId: message.chatId,
136268
+ messageId: message.messageId
136269
+ };
136270
+ const run2 = (execute2) => runInbound(core, target2, message.messageId, execute2, {
136271
+ rememberDeferred: () => deps.acknowledgements.rememberDeferred(identity2, target2),
136272
+ clearDeferred: () => deps.acknowledgements.clearDeferred(identity2),
136273
+ complete
136274
+ });
135942
136275
  const continueMessage = async (target3) => {
135943
136276
  const boundThread = chatOf(core.state, chatKey).threadId;
135944
136277
  const answer = boundThread === null ? void 0 : questionFlow.textAnswer(chatKey, boundThread, message.text);
135945
136278
  if (answer !== void 0) {
135946
136279
  const route2 = { kind: "question", answer };
135947
136280
  await saveRoute(route2);
135948
- await execute(route2, target3);
135949
- return;
136281
+ return execute(route2, target3);
135950
136282
  }
135951
136283
  const project = await projectOrNull(host.projects, target3.projectId);
135952
136284
  if (project === null || project.archivedAt != null) {
@@ -135958,14 +136290,16 @@ var makeInbound = (core, deps) => {
135958
136290
  projectId: target3.projectId
135959
136291
  });
135960
136292
  }
135961
- if (archivedReplyCooldown.tryAcquire(chatKey)) {
135962
- await deliver(
135963
- target3,
135964
- project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
135965
- );
135966
- }
135967
- await settleInbound(core, target3, message.messageId);
135968
- return;
136293
+ return {
136294
+ status: "settled",
136295
+ respond: async () => {
136296
+ if (archivedReplyCooldown.tryAcquire(chatKey))
136297
+ await deliver(
136298
+ target3,
136299
+ project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
136300
+ );
136301
+ }
136302
+ };
135969
136303
  }
135970
136304
  const routed = targetOf(target3);
135971
136305
  const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
@@ -135973,20 +136307,21 @@ var makeInbound = (core, deps) => {
135973
136307
  if (attachmentsOnly) {
135974
136308
  await saveRoute({ kind: "unavailable" });
135975
136309
  await complete();
135976
- await deliver(target3, t("reply.attachmentsNoTeam"));
135977
- await settleInbound(core, target3, message.messageId);
135978
- return;
136310
+ return {
136311
+ status: "settled",
136312
+ respond: async () => {
136313
+ await deliver(target3, t("reply.attachmentsNoTeam"));
136314
+ }
136315
+ };
135979
136316
  }
135980
136317
  const route2 = { kind: "team", chatId: target3.chatId, projectId: target3.projectId };
135981
136318
  await saveRoute(route2);
135982
- await execute(route2, target3);
135983
- return;
136319
+ return execute(route2, target3);
135984
136320
  }
135985
136321
  if (attachmentsOnly) {
135986
136322
  const route2 = { kind: "attachments" };
135987
136323
  await saveRoute(route2);
135988
- await execute(route2, target3);
135989
- return;
136324
+ return execute(route2, target3);
135990
136325
  }
135991
136326
  const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
135992
136327
  const route = {
@@ -135995,62 +136330,65 @@ var makeInbound = (core, deps) => {
135995
136330
  projectId: target3.projectId
135996
136331
  };
135997
136332
  await saveRoute(route);
135998
- await execute(route, target3);
136333
+ return execute(route, target3);
135999
136334
  };
136000
136335
  const execute = (route, routedTarget = target2) => executeInboundRoute(
136001
136336
  core,
136002
- { ...deps, openTurn, acceptedWork, inheritFromParent, continueMessage },
136003
- { current, message, target: routedTarget, route, work, queueChatId }
136337
+ { ...deps, openTurn, acceptedWork, inheritFromParent, continueMessage, run: run2 },
136338
+ { current, message, target: routedTarget, route, work, queueChatId, onAccepted }
136004
136339
  );
136005
- if (work?.route !== void 0) {
136006
- await execute(work.route);
136007
- return;
136008
- }
136009
- if (!isAllowedSender(target2, message.senderId, message.chatKind)) {
136010
- await saveRoute({ kind: "unavailable" });
136011
- await complete();
136012
- turnAwayStranger(target2, message.senderId);
136013
- await settleInbound(core, target2, message.messageId);
136014
- return;
136015
- }
136016
- if (message.unsupportedType !== void 0) {
136017
- await saveRoute({ kind: "unavailable" });
136018
- await complete();
136019
- if (unsupportedReplyCooldown.tryAcquire(chatKey))
136020
- await deliver(
136021
- target2,
136022
- message.unsupportedType === "folder" ? t("reply.unsupportedFolder") : message.unsupportedType === "voice" ? t("reply.unsupportedVoice") : t("reply.unsupportedType", { type: message.unsupportedType })
136023
- );
136024
- await settleInbound(core, target2, message.messageId);
136025
- return;
136026
- }
136027
- const approval = openApprovalOf(core.state, chatKey);
136028
- const token = approval === void 0 ? null : adapterFor(target2.platform)?.answerByText?.(target2, message.text, approval.options) ?? null;
136029
- if (token !== null) {
136030
- const route = { kind: "approval", token };
136031
- await saveRoute(route);
136032
- await execute(route);
136033
- return;
136034
- }
136035
- const command = parseChatCommand({
136036
- text: message.text,
136037
- chinese: t.language === "zh-CN",
136038
- hasImages: message.resources.length > 0
136039
- });
136040
- if (command.kind !== "message") {
136041
- const route = { kind: "command", command };
136042
- await saveRoute(route);
136043
- await execute(route);
136044
- return;
136045
- }
136046
- if (target2.projectId === "") {
136047
- const decision = await decideProjectMessage(target2, message);
136048
- const route = { kind: "project", decision };
136049
- await saveRoute(route);
136050
- await execute(route);
136051
- return;
136052
- }
136053
- await continueMessage(target2);
136340
+ let onAccepted;
136341
+ const executeMessage = async (accepted) => {
136342
+ onAccepted = accepted;
136343
+ inheritFromParent(current, message.chatId, message.parentChatId);
136344
+ target2 = { ...bindingOf2(current, message.chatId), reply: target2.reply };
136345
+ if (work?.route !== void 0) return execute(work.route);
136346
+ if (!isAllowedSender(target2, message.senderId, message.chatKind)) {
136347
+ await saveRoute({ kind: "unavailable" });
136348
+ await complete();
136349
+ return { status: "settled", respond: () => turnAwayStranger(target2, message.senderId) };
136350
+ }
136351
+ const unsupportedType = message.unsupportedType;
136352
+ if (unsupportedType !== void 0) {
136353
+ await saveRoute({ kind: "unavailable" });
136354
+ await complete();
136355
+ return {
136356
+ status: "settled",
136357
+ respond: async () => {
136358
+ if (unsupportedReplyCooldown.tryAcquire(chatKey))
136359
+ await deliver(
136360
+ target2,
136361
+ unsupportedType === "folder" ? t("reply.unsupportedFolder") : unsupportedType === "voice" ? t("reply.unsupportedVoice") : t("reply.unsupportedType", { type: unsupportedType })
136362
+ );
136363
+ }
136364
+ };
136365
+ }
136366
+ const approval = openApprovalOf(core.state, chatKey);
136367
+ const token = approval === void 0 ? null : adapterFor(target2.platform)?.answerByText?.(target2, message.text, approval.options) ?? null;
136368
+ if (token !== null) {
136369
+ const route = { kind: "approval", token };
136370
+ await saveRoute(route);
136371
+ return execute(route);
136372
+ }
136373
+ const command = parseChatCommand({
136374
+ text: message.text,
136375
+ chinese: t.language === "zh-CN",
136376
+ hasImages: message.resources.length > 0
136377
+ });
136378
+ if (command.kind !== "message") {
136379
+ const route = { kind: "command", command };
136380
+ await saveRoute(route);
136381
+ return execute(route);
136382
+ }
136383
+ if (target2.projectId === "") {
136384
+ const decision = await decideProjectMessage(target2, message);
136385
+ const route = { kind: "project", decision };
136386
+ await saveRoute(route);
136387
+ return execute(route);
136388
+ }
136389
+ return continueMessage(target2);
136390
+ };
136391
+ await run2(executeMessage);
136054
136392
  };
136055
136393
  const inboundHandlersFor = (bot) => ({
136056
136394
  onError: (message) => host.log.warn("chat inbound connection failed", {
@@ -136137,7 +136475,10 @@ var makeInbound = (core, deps) => {
136137
136475
  inboundHandlersFor,
136138
136476
  resumeMessage,
136139
136477
  enqueueReplay,
136140
- cancelReplay: replay.cancelReplay,
136478
+ cancelReplay: (rowId) => {
136479
+ replay.cancelReplay(rowId);
136480
+ track(deps.acknowledgements.stopDeferred(rowId));
136481
+ },
136141
136482
  stop: async () => {
136142
136483
  accepting = false;
136143
136484
  replay.stop();
@@ -137410,7 +137751,7 @@ var ConsoleError = class extends Error {
137410
137751
  this.retryAfterMs = extra.retryAfterMs;
137411
137752
  }
137412
137753
  };
137413
- var withDeadline = async (deps, ms, run2) => {
137754
+ var withDeadline2 = async (deps, ms, run2) => {
137414
137755
  const controller = new AbortController();
137415
137756
  const timer = new AbortController();
137416
137757
  let timedOut = false;
@@ -137437,7 +137778,7 @@ var withDeadline = async (deps, ms, run2) => {
137437
137778
  deps.signal.removeEventListener("abort", onParentAbort);
137438
137779
  }
137439
137780
  };
137440
- var guarded = withDeadline;
137781
+ var guarded = withDeadline2;
137441
137782
  var sleepOrAbort = async (deps, ms) => {
137442
137783
  try {
137443
137784
  await deps.clock.sleep(ms, deps.signal);
@@ -137469,7 +137810,7 @@ var retryAfterMs = (response) => {
137469
137810
  };
137470
137811
  var openConsole = async (deps, jar) => {
137471
137812
  const pageUrl = `${CONSOLE_ORIGIN}${CONSOLE_APP_PATH}`;
137472
- const loadPage = () => withDeadline(deps, TIMEOUT_MS.page, async (signal) => {
137813
+ const loadPage = () => withDeadline2(deps, TIMEOUT_MS.page, async (signal) => {
137473
137814
  const { response, finalUrl } = await jar.fetch(deps.fetch, pageUrl, {
137474
137815
  method: "GET",
137475
137816
  headers: { accept: "text/html,application/xhtml+xml" },
@@ -137498,7 +137839,7 @@ var openConsole = async (deps, jar) => {
137498
137839
  calls += 1;
137499
137840
  const started = deps.clock.now();
137500
137841
  try {
137501
- const result2 = await withDeadline(deps, timeoutOf(kind), async (signal) => {
137842
+ const result2 = await withDeadline2(deps, timeoutOf(kind), async (signal) => {
137502
137843
  const { response } = await jar.fetch(deps.fetch, `${origin}${path}`, {
137503
137844
  method: "POST",
137504
137845
  headers: {
@@ -145068,22 +145409,6 @@ var larkInboundPersistence = (contextFor) => ({
145068
145409
  }
145069
145410
  });
145070
145411
 
145071
- // src/deadline.ts
145072
- var withDeadline2 = async (timeoutMs, load) => {
145073
- const signal = AbortSignal.timeout(timeoutMs);
145074
- let onAbort = () => {
145075
- };
145076
- const aborted = new Promise((_, reject) => {
145077
- onAbort = () => reject(signal.reason);
145078
- signal.addEventListener("abort", onAbort, { once: true });
145079
- });
145080
- try {
145081
- return await Promise.race([load(signal), aborted]);
145082
- } finally {
145083
- signal.removeEventListener("abort", onAbort);
145084
- }
145085
- };
145086
-
145087
145412
  // src/boundedSeen.ts
145088
145413
  var makeBoundedSeen = (limit) => {
145089
145414
  const seen = /* @__PURE__ */ new Set();
@@ -145229,7 +145554,7 @@ var errorReason = (error) => {
145229
145554
  };
145230
145555
  var loadReplyReference = async (load) => {
145231
145556
  try {
145232
- return await withDeadline2(LOAD_TIMEOUT_MS, load) ?? { unavailableReason: "not-found" };
145557
+ return await withDeadline(LOAD_TIMEOUT_MS, load) ?? { unavailableReason: "not-found" };
145233
145558
  } catch (error) {
145234
145559
  return { unavailableReason: errorReason(error) };
145235
145560
  }
@@ -145248,7 +145573,7 @@ var makeLookup = (rowId, load) => {
145248
145573
  if (known !== void 0 && Date.now() - known.at < CACHE_MS) return known.value;
145249
145574
  const pending = inflight.get(key2);
145250
145575
  if (pending !== void 0) return pending;
145251
- const reading = withDeadline2(LOOKUP_TIMEOUT_MS, (signal) => load(id, signal)).catch(() => null).then((value2) => {
145576
+ const reading = withDeadline(LOOKUP_TIMEOUT_MS, (signal) => load(id, signal)).catch(() => null).then((value2) => {
145252
145577
  cache.set(key2, { value: value2, at: Date.now() });
145253
145578
  return value2;
145254
145579
  }).finally(() => inflight.delete(key2));
@@ -145428,7 +145753,7 @@ ${indent} `)}`;
145428
145753
  };
145429
145754
  var loadMergeForward = async (message, deps) => {
145430
145755
  try {
145431
- const items = await withDeadline2(1e4, async (signal) => {
145756
+ const items = await withDeadline(1e4, async (signal) => {
145432
145757
  const token = await deps.tenantToken();
145433
145758
  signal.throwIfAborted();
145434
145759
  return fetchMergeForwardMessages({
@@ -145848,6 +146173,8 @@ var en_default = {
145848
146173
  sendingFile: "Sending {{name}}:",
145849
146174
  notAllowedYet: `You are not on the allow list yet. Your user id is {{id}}. Add it under Settings \u2192 Plugins \u2192 Chat, in this bot's "Allowed people" box.`,
145850
146175
  received: "Got it, working on it.",
146176
+ inboundFailed: "This message couldn't be handled. Please send it again later.",
146177
+ inboundDeferred: "No need to send this again \u2014 it will be handled automatically the next time the machine starts.",
145851
146178
  welcome: "Connected. Send text or images to get started, or send /help to see the available commands."
145852
146179
  },
145853
146180
  approval: {
@@ -145932,9 +146259,10 @@ var en_default = {
145932
146259
  new: "\xB7 /new \u2014 forget the current session; the next message starts a new one",
145933
146260
  team: "\xB7 /team \u2014 give this project's team a goal with each message; use /repo to pick the project first",
145934
146261
  thread: "\xB7 /thread \u2014 go back to a normal session",
145935
- aliases: "Chinese shortcuts (\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
146262
+ aliases: "Chinese shortcuts (\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u4F1A\u8BDD\u5386\u53F2 / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
145936
146263
  sessions: "\xB7 /sessions \u2014 list this project's sessions; add a project number from /repo to list the ones this bot opened in another project",
145937
146264
  resume: "\xB7 /resume number-or-id-prefix \u2014 switch sessions; one from another project switches the project too",
146265
+ history: "\xB7 /history [count] \u2014 preview recent messages in the current session, in private chats only (default {{defaultCount}}, maximum {{maxCount}})",
145938
146266
  model: "\xB7 /model \u2014 see the model this chat uses; /model number or /model instance/model switches it",
145939
146267
  preset: "\xB7 /preset \u2014 see the preset this chat uses for new sessions; /preset number or /preset name-or-id switches it; /preset none clears the chat choice",
145940
146268
  plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
@@ -145943,6 +146271,21 @@ var en_default = {
145943
146271
  group: "\xB7 /group name \u2014 Lark only: the bot creates a group and adds you (bots on other platforms cannot)",
145944
146272
  syncNote: "\xB7 After a private chat attaches a session, web user text and final assistant text sync here (groups never sync)."
145945
146273
  },
146274
+ history: {
146275
+ usage: "Usage: /history [count] \u2014 1 to {{maxCount}} (larger numbers count as {{maxCount}}), default {{defaultCount}}.",
146276
+ directOnly: "In a group this would show the session to everyone. Send /history in a private chat with the bot.",
146277
+ noSession: "This chat has no current session. Send a message to start one, or use /sessions to find a session and /resume to switch to it.",
146278
+ empty: "No messages to show in this session yet.",
146279
+ noneRecent: "Nothing to show among this session's most recent messages (older ones were not checked).",
146280
+ failed: "Could not read the recent messages. Try again in a moment.",
146281
+ title: "Recent messages in this session ({{count}}):",
146282
+ user: "User",
146283
+ assistant: "Assistant",
146284
+ image: "[Image]",
146285
+ file: "[File]",
146286
+ noText: "[No previewable text]",
146287
+ truncated: "[Remaining text omitted]"
146288
+ },
145946
146289
  sessions: {
145947
146290
  title: "This project's sessions ({{count}}):",
145948
146291
  empty: "This bot has not opened any session in this project yet. Send a message to start one.",
@@ -146755,6 +147098,8 @@ var zh_CN_default = {
146755
147098
  sendingFile: "\u6B63\u5728\u53D1\u9001 {{name}}\uFF1A",
146756
147099
  notAllowedYet: "\u4F60\u8FD8\u4E0D\u5728\u5141\u8BB8\u540D\u5355\u91CC\uFF0C\u4F60\u7684\u7528\u6237 id \u662F {{id}}\uFF0C\u8BF7\u628A\u5B83\u586B\u8FDB \u8BBE\u7F6E \u2192 \u63D2\u4EF6 \u2192 \u804A\u5929 \u2192 \u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002",
146757
147100
  received: "\u6536\u5230\uFF0C\u5728\u5904\u7406\u3002",
147101
+ inboundFailed: "\u8FD9\u6761\u6D88\u606F\u5904\u7406\u65F6\u51FA\u4E86\u9519\uFF0C\u8BF7\u7A0D\u540E\u91CD\u53D1\u3002",
147102
+ inboundDeferred: "\u4E0D\u7528\u91CD\u53D1\uFF1A\u673A\u5668\u4E0B\u6B21\u542F\u52A8\u65F6\u4F1A\u81EA\u52A8\u5904\u7406\u8FD9\u6761\u6D88\u606F\u3002",
146758
147103
  welcome: "\u5DF2\u8FDE\u4E0A\u3002\u76F4\u63A5\u53D1\u6587\u5B57\u6216\u56FE\u7247\u5C31\u80FD\u5F00\u59CB\uFF0C\u53D1 /help \u770B\u80FD\u7528\u7684\u547D\u4EE4\u3002"
146759
147104
  },
146760
147105
  approval: {
@@ -146839,9 +147184,10 @@ var zh_CN_default = {
146839
147184
  new: "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
146840
147185
  team: "\xB7 /team \u2014\u2014 \u6BCF\u6761\u6D88\u606F\u90FD\u7ED9\u8FD9\u4E2A\u9879\u76EE\u7684\u56E2\u961F\u5F00\u4E00\u4E2A\u76EE\u6807\uFF1B\u5148\u7528 /repo \u9009\u9879\u76EE",
146841
147186
  thread: "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
146842
- aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
147187
+ aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u4F1A\u8BDD\u5386\u53F2 / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
146843
147188
  sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u9879\u76EE\u7684\u4F1A\u8BDD\uFF1B\u52A0 /repo \u91CC\u7684\u9879\u76EE\u7F16\u53F7\uFF0C\u5217\u51FA\u672C\u673A\u5668\u4EBA\u5728\u522B\u7684\u9879\u76EE\u5F00\u8FC7\u7684",
146844
147189
  resume: "\xB7 /resume \u7F16\u53F7\u6216 id \u5F00\u5934 \u2014\u2014 \u5207\u4F1A\u8BDD\uFF1B\u522B\u7684\u9879\u76EE\u7684\u4F1A\u8BDD\u4F1A\u8FDE\u540C\u9879\u76EE\u4E00\u8D77\u5207",
147190
+ history: "\xB7 /history [\u6570\u91CF] \u2014\u2014 \u4EC5\u79C1\u804A\uFF1A\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1\u7684\u6D88\u606F\uFF08\u9ED8\u8BA4 {{defaultCount}} \u6761\uFF0C\u6700\u591A {{maxCount}} \u6761\uFF09",
146845
147191
  model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
146846
147192
  preset: "\xB7 /preset \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u65B0\u4F1A\u8BDD\u7528\u7684\u9884\u8BBE\uFF1B/preset \u7F16\u53F7 \u6216 /preset \u540D\u5B57\u6216 id \u5207\u6362\uFF1B/preset none \u6E05\u6389\u804A\u5929\u9009\u62E9",
146847
147193
  plan: "\xB7 /plan \u2014\u2014 \u5F00\u5173\u8BA1\u5212\u6A21\u5F0F\uFF08agent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\uFF09\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A",
@@ -146850,6 +147196,21 @@ var zh_CN_default = {
146850
147196
  group: "\xB7 /group \u7FA4\u540D \u2014\u2014 \u4EC5\u98DE\u4E66\uFF1A\u673A\u5668\u4EBA\u5EFA\u4E00\u4E2A\u7FA4\u5E76\u628A\u4F60\u62C9\u8FDB\u53BB\uFF08\u522B\u7684\u5E73\u53F0\u7684\u673A\u5668\u4EBA\u5EFA\u4E0D\u4E86\u7FA4\uFF09",
146851
147197
  syncNote: "\xB7 \u79C1\u804A\u9644\u7740\u4F1A\u8BDD\u540E\uFF0C\u7F51\u9875\u4E0A\u7684\u7528\u6237\u6587\u5B57\u4E0E\u7EC8\u6001\u52A9\u624B\u6587\u5B57\u4F1A\u540C\u6B65\u5230\u6B64\uFF08\u7FA4\u804A\u4E0D\u540C\u6B65\uFF09\u3002"
146852
147198
  },
147199
+ history: {
147200
+ usage: "\u7528\u6CD5\uFF1A/history [\u6570\u91CF] \u2014\u2014 1\u2013{{maxCount}} \u6761\uFF08\u8D85\u51FA\u6309 {{maxCount}} \u6761\u7B97\uFF09\uFF0C\u9ED8\u8BA4 {{defaultCount}} \u6761\u3002",
147201
+ directOnly: "\u5728\u7FA4\u91CC\u4F1A\u628A\u4F1A\u8BDD\u5185\u5BB9\u53D1\u7ED9\u6240\u6709\u4EBA\u3002\u5728\u4E0E\u673A\u5668\u4EBA\u7684\u79C1\u804A\u91CC\u53D1 /history\u3002",
147202
+ noSession: "\u8FD9\u4E2A\u804A\u5929\u8FD8\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u3002\u53D1\u4E00\u53E5\u8BDD\u5F00\u4E00\u4E2A\uFF0C\u6216\u5148\u7528 /sessions \u627E\u5230\u4F1A\u8BDD\uFF0C\u518D\u7528 /resume \u5207\u8FC7\u53BB\u3002",
147203
+ empty: "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u80FD\u67E5\u770B\u7684\u6D88\u606F\u3002",
147204
+ noneRecent: "\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1\u7684\u6D88\u606F\u91CC\u6CA1\u6709\u80FD\u67E5\u770B\u7684\uFF08\u66F4\u65E9\u7684\u6CA1\u6709\u5F80\u524D\u7FFB\uFF09\u3002",
147205
+ failed: "\u6CA1\u80FD\u8BFB\u53D6\u6700\u8FD1\u7684\u6D88\u606F\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
147206
+ title: "\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1\u7684\u6D88\u606F\uFF08{{count}} \u6761\uFF09\uFF1A",
147207
+ user: "\u7528\u6237",
147208
+ assistant: "\u52A9\u624B",
147209
+ image: "[\u56FE\u7247]",
147210
+ file: "[\u6587\u4EF6]",
147211
+ noText: "[\u6CA1\u6709\u53EF\u9884\u89C8\u7684\u6587\u5B57]",
147212
+ truncated: "[\u5176\u4F59\u6587\u5B57\u5DF2\u7701\u7565]"
147213
+ },
146853
147214
  sessions: {
146854
147215
  title: "\u672C\u9879\u76EE\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
146855
147216
  empty: "\u672C\u673A\u5668\u4EBA\u8FD8\u6CA1\u5728\u672C\u9879\u76EE\u5F00\u8FC7\u4F1A\u8BDD\uFF0C\u53D1\u4E00\u53E5\u8BDD\u5C31\u4F1A\u5F00\u4E00\u6761\u3002",
@@ -147604,7 +147965,7 @@ var CREATE_INTERVAL_MS = 1100;
147604
147965
  var syncLarkSlashCommands = async (input) => {
147605
147966
  const request = async (method, id, body) => {
147606
147967
  input.signal.throwIfAborted();
147607
- return withDeadline2(REQUEST_TIMEOUT_MS, async (timeout2) => {
147968
+ return withDeadline(REQUEST_TIMEOUT_MS, async (timeout2) => {
147608
147969
  const signal = AbortSignal.any([input.signal, timeout2]);
147609
147970
  const response = await input.http(
147610
147971
  id === void 0 ? ENDPOINT : `${ENDPOINT}/${encodeURIComponent(id)}`,
@@ -147681,7 +148042,7 @@ var makeLarkSlashCommands = (deps) => {
147681
148042
  const run2 = app.settled.then(async () => {
147682
148043
  if (signal.aborted) return;
147683
148044
  try {
147684
- const token = await withDeadline2(
148045
+ const token = await withDeadline(
147685
148046
  REQUEST_TIMEOUT_MS,
147686
148047
  (timeout2) => deps.tenantToken(bot, AbortSignal.any([signal, timeout2]))
147687
148048
  );
@@ -148159,14 +148520,14 @@ var makeLarkAdapter = (deps) => {
148159
148520
  }),
148160
148521
  clearAcknowledge: async (binding, messageId, ackId, outcome) => {
148161
148522
  const token = await tenantToken(binding);
148162
- await withDeadline2(
148523
+ await withDeadline(
148163
148524
  LARK_REACTION_TIMEOUT_MS,
148164
148525
  (signal) => removeReaction({ http: deps.http, token, messageId, reactionId: ackId, signal })
148165
148526
  );
148166
148527
  const emojiType = outcome === "done" ? LARK_DONE_REACTION : outcome === "failed" ? LARK_FAILED_REACTION : null;
148167
148528
  if (emojiType === null) return;
148168
148529
  try {
148169
- await withDeadline2(
148530
+ await withDeadline(
148170
148531
  LARK_REACTION_TIMEOUT_MS,
148171
148532
  (signal) => addReaction({ http: deps.http, token, messageId, emojiType, signal })
148172
148533
  );
@@ -148436,7 +148797,7 @@ var outcomeOfSession = (status) => {
148436
148797
  var THREAD_PROJECT_CACHE_LIMIT = 256;
148437
148798
  var makeNotifications = (core, deps) => {
148438
148799
  const { host, adapterFor, bindingForChatKey, ready } = core;
148439
- const { takeWorking } = deps;
148800
+ const { takeWorking, stopTypingFor } = deps;
148440
148801
  const { turnCards } = deps.replyCards;
148441
148802
  const { failures, conclusions, delivery } = deps;
148442
148803
  const { recoverConclusion, recoverConclusions } = makeConclusionRecovery(
@@ -148489,6 +148850,7 @@ var makeNotifications = (core, deps) => {
148489
148850
  const threadId = payload.threadId;
148490
148851
  if (typeof threadId !== "string") return;
148491
148852
  if (payload.session?.activeTurnId != null) return;
148853
+ stopTypingFor(threadId);
148492
148854
  const outcome = outcomeOfSession(payload.session?.status);
148493
148855
  const said = threadsWithSomethingToReport.delete(threadId);
148494
148856
  await ready;
@@ -148533,6 +148895,7 @@ var makeNotifications = (core, deps) => {
148533
148895
  host.events.on("thread.failed", async (event) => {
148534
148896
  const threadId = event.threadId;
148535
148897
  if (threadId === null) return;
148898
+ stopTypingFor(threadId);
148536
148899
  threadsWithSomethingToReport.delete(threadId);
148537
148900
  await ready;
148538
148901
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
@@ -150072,6 +150435,7 @@ var makeTeamGoals = (core) => {
150072
150435
  };
150073
150436
  const startTeamGoal = async (binding, message, accepted) => {
150074
150437
  let goal;
150438
+ let confirmConsumed;
150075
150439
  try {
150076
150440
  const requestId = teamGoalRequestId(binding, message.messageId);
150077
150441
  const topicRoot = await message.topicRootForSession?.(requestId);
@@ -150086,24 +150450,30 @@ var makeTeamGoals = (core) => {
150086
150450
  }
150087
150451
  });
150088
150452
  if (isRecord3(result2) && result2._tag === "TeamGoalRejection") {
150089
- await deliver(
150090
- binding,
150091
- t(
150092
- result2.code === "no-roster" ? "team.noRoster" : result2.code === "invalid-goal" ? "team.invalidGoal" : "team.startFailed"
150093
- )
150094
- );
150095
- return null;
150453
+ const key2 = result2.code === "no-roster" ? "team.noRoster" : result2.code === "invalid-goal" ? "team.invalidGoal" : "team.startFailed";
150454
+ return {
150455
+ status: "settled",
150456
+ respond: async () => {
150457
+ await deliver(binding, t(key2));
150458
+ }
150459
+ };
150096
150460
  }
150097
150461
  if (!isRecord3(result2) || typeof result2.leadThreadId !== "string" || result2.leadThreadId === "" || result2.leadName !== null && typeof result2.leadName !== "string") {
150098
150462
  throw new Error("team startGoal returned an invalid result");
150099
150463
  }
150100
- topicRoot?.confirmConsumed();
150101
150464
  goal = { leadThreadId: result2.leadThreadId, leadName: result2.leadName };
150465
+ confirmConsumed = () => topicRoot?.confirmConsumed();
150102
150466
  } catch (error) {
150103
150467
  host.log.warn("chat could not start the team goal", { error: describeError(error) });
150104
- await deliver(binding, t("team.startFailed"));
150105
- return null;
150468
+ return {
150469
+ status: "settled",
150470
+ respond: async () => {
150471
+ await deliver(binding, t("team.startFailed"));
150472
+ }
150473
+ };
150106
150474
  }
150475
+ accepted?.onAccepted?.();
150476
+ confirmConsumed();
150107
150477
  await commitDurably("team-goal-started", {
150108
150478
  chatKey: chatKeyOf(binding),
150109
150479
  threadId: goal.leadThreadId
@@ -150117,7 +150487,7 @@ var makeTeamGoals = (core) => {
150117
150487
  bot: binding.displayName,
150118
150488
  threadId: goal.leadThreadId
150119
150489
  });
150120
- return goal.leadThreadId;
150490
+ return { status: "started", claimsPlaceholder: false };
150121
150491
  };
150122
150492
  return { hasRosterFor, startTeamGoal };
150123
150493
  };
@@ -151350,12 +151720,11 @@ var dispatchTurnWithReceipt = async (host, input, options) => {
151350
151720
  var DELAYS = [3e4, 12e4, 6e5];
151351
151721
  var MAX_DISPATCH_ATTEMPTS = DELAYS.length + 1;
151352
151722
  var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried, messageText2, firstAttachmentName) => {
151353
- if (deps.signal.aborted) return false;
151723
+ if (deps.signal.aborted) return { status: "deferred" };
151354
151724
  const previous = inbox?.entry?.attempts ?? 0;
151355
151725
  const attempt = Math.min(previous + 1, MAX_DISPATCH_ATTEMPTS);
151356
151726
  const nextAttemptAt = inbox?.entry?.nextAttemptAt;
151357
151727
  const incremented = previous < MAX_DISPATCH_ATTEMPTS;
151358
- if (incremented) await inbox?.saveRetry(attempt, nextAttemptAt);
151359
151728
  const reference2 = dispatchFailureReference(input.requestId);
151360
151729
  const notify = async (text3) => {
151361
151730
  try {
@@ -151368,6 +151737,18 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151368
151737
  });
151369
151738
  }
151370
151739
  };
151740
+ if (incremented) {
151741
+ try {
151742
+ await inbox?.saveRetry(attempt, nextAttemptAt);
151743
+ } catch (error) {
151744
+ core.host.log.warn("chat could not save a dispatch attempt; leaving it for replay", {
151745
+ threadId: input.threadId,
151746
+ requestId: input.requestId,
151747
+ error: describeError(error)
151748
+ });
151749
+ return { status: "settled", respond: () => notify(core.t("reply.inboundDeferred")) };
151750
+ }
151751
+ }
151371
151752
  try {
151372
151753
  if (incremented) {
151373
151754
  await dispatchTurnWithReceipt(core.host, input, {
@@ -151376,14 +151757,14 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151376
151757
  });
151377
151758
  } else {
151378
151759
  const status = await confirmTurnReceipt(core.host, input);
151379
- if (deps.signal.aborted) return false;
151760
+ if (deps.signal.aborted) return { status: "deferred" };
151380
151761
  if (status === "absent") throw new Error("attempt budget exhausted; receipt absent");
151381
151762
  }
151382
- return true;
151763
+ return { status: "accepted" };
151383
151764
  } catch (error) {
151384
151765
  if (deps.signal.aborted) {
151385
151766
  if (incremented) await inbox?.saveRetry(previous, nextAttemptAt);
151386
- return false;
151767
+ return { status: "deferred" };
151387
151768
  }
151388
151769
  const delay2 = DELAYS[attempt - 1];
151389
151770
  if (!(error instanceof TurnDispatchRejected) && delay2 !== void 0 && inbox?.entry !== void 0) {
@@ -151397,7 +151778,7 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151397
151778
  error: describeError(error)
151398
151779
  });
151399
151780
  if (attempt === 1) await notify(deps.failures.forDispatchRetry(input.requestId));
151400
- return false;
151781
+ return { status: "deferred" };
151401
151782
  }
151402
151783
  const redactor = botRedactor(
151403
151784
  core.adapterFor(binding.platform)?.columns ?? [],
@@ -151416,10 +151797,12 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151416
151797
  const textExcerpt = messageText2.trim();
151417
151798
  const attachmentName = firstAttachmentName?.trim() ?? "";
151418
151799
  const identifyingNotice = textExcerpt !== "" ? deps.failures.forDispatchFailureQuoted(input.requestId, textExcerpt) : attachmentName !== "" ? deps.failures.forDispatchFailureAttachment(input.requestId, attachmentName) : void 0;
151419
- await notify(
151420
- binding.reply === void 0 && attempt > 1 && identifyingNotice !== void 0 ? identifyingNotice : failureNotice
151421
- );
151422
- return false;
151800
+ return {
151801
+ status: "settled",
151802
+ respond: () => notify(
151803
+ binding.reply === void 0 && attempt > 1 && identifyingNotice !== void 0 ? identifyingNotice : failureNotice
151804
+ )
151805
+ };
151423
151806
  }
151424
151807
  };
151425
151808
 
@@ -151473,6 +151856,7 @@ var makeTurns = (core, deps) => {
151473
151856
  const ensureThread = async (binding, message) => {
151474
151857
  const chatKey = chatKeyOf(binding);
151475
151858
  let threadId = chatOf(core.state, chatKey).threadId ?? void 0;
151859
+ let dropped = false;
151476
151860
  if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
151477
151861
  host.log.info("chat dropped a saved thread that is no longer usable", {
151478
151862
  bot: binding.displayName,
@@ -151480,7 +151864,7 @@ var makeTurns = (core, deps) => {
151480
151864
  });
151481
151865
  bindChat(chatKey, null);
151482
151866
  threadId = void 0;
151483
- await deliver(binding, t("reply.threadGone"));
151867
+ dropped = true;
151484
151868
  }
151485
151869
  if (threadId !== void 0) return threadId;
151486
151870
  const project = await projectOrNull2(host.projects, binding.projectId);
@@ -151489,7 +151873,13 @@ var makeTurns = (core, deps) => {
151489
151873
  bot: binding.displayName,
151490
151874
  projectId: binding.projectId
151491
151875
  });
151492
- return null;
151876
+ return {
151877
+ status: "settled",
151878
+ respond: async () => {
151879
+ if (dropped) await deliver(binding, t("reply.threadGone"));
151880
+ await deliver(binding, t("reply.projectMissing"));
151881
+ }
151882
+ };
151493
151883
  }
151494
151884
  const modelSelection = await resolveDefaultModel(
151495
151885
  binding,
@@ -151502,8 +151892,13 @@ var makeTurns = (core, deps) => {
151502
151892
  bot: binding.displayName,
151503
151893
  projectId: binding.projectId
151504
151894
  });
151505
- await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
151506
- return null;
151895
+ return {
151896
+ status: "settled",
151897
+ respond: async () => {
151898
+ if (dropped) await deliver(binding, t("reply.threadGone"));
151899
+ await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
151900
+ }
151901
+ };
151507
151902
  }
151508
151903
  const createdThreadId = identity2("thread", binding, message);
151509
151904
  const presetId = await resolveDefaultPreset(chatOf(core.state, chatKey).preset, binding, host);
@@ -151526,17 +151921,19 @@ var makeTurns = (core, deps) => {
151526
151921
  openedAt: (/* @__PURE__ */ new Date()).toISOString()
151527
151922
  });
151528
151923
  rememberThreadProject(createdThreadId, binding.projectId);
151924
+ if (dropped) await deliver(binding, t("reply.threadGone"));
151529
151925
  return createdThreadId;
151530
151926
  };
151531
151927
  const startTurn = async (binding, message, accepted, inbox) => {
151532
151928
  const chatKey = chatKeyOf(binding);
151533
151929
  const text3 = message.text;
151534
151930
  const replayed = inbox?.replayed === true || (inbox?.entry?.attempts ?? 0) > 0;
151535
- const threadId = inbox?.entry?.threadId ?? await ensureThread(binding, message);
151536
- if (threadId === null) {
151931
+ const prepared = inbox?.entry?.threadId ?? await ensureThread(binding, message);
151932
+ if (typeof prepared !== "string") {
151537
151933
  await inbox?.complete();
151538
- return null;
151934
+ return prepared;
151539
151935
  }
151936
+ const threadId = prepared;
151540
151937
  await inbox?.saveThread(threadId);
151541
151938
  const topicRoot = await message.topicRootForSession?.(threadId);
151542
151939
  const requestId = identity2("turn", binding, message);
@@ -151570,11 +151967,33 @@ var makeTurns = (core, deps) => {
151570
151967
  stored: [...saved.stored, ...own.attachments],
151571
151968
  notes: [...saved.notes, ...own.notes]
151572
151969
  };
151573
- await inbox?.saveAttachments(saved);
151574
151970
  } catch (error) {
151575
151971
  restoreCarried();
151576
151972
  throw error;
151577
151973
  }
151974
+ try {
151975
+ await inbox?.saveAttachments(saved);
151976
+ } catch (error) {
151977
+ restoreCarried();
151978
+ host.log.warn("chat could not save inbound attachments; leaving them for replay", {
151979
+ threadId,
151980
+ error: describeError(error)
151981
+ });
151982
+ return {
151983
+ status: "settled",
151984
+ respond: async () => {
151985
+ try {
151986
+ if (!await deliver(binding, t("reply.inboundDeferred")))
151987
+ host.log.warn("chat could not deliver an inbound deferral notice", { threadId });
151988
+ } catch (deliveryError) {
151989
+ host.log.warn("chat could not deliver an inbound deferral notice", {
151990
+ threadId,
151991
+ error: describeError(deliveryError)
151992
+ });
151993
+ }
151994
+ }
151995
+ };
151996
+ }
151578
151997
  }
151579
151998
  const attachments = saved.stored;
151580
151999
  const notes = saved.notes;
@@ -151634,7 +152053,8 @@ var makeTurns = (core, deps) => {
151634
152053
  message.text,
151635
152054
  message.resources[0]?.name
151636
152055
  );
151637
- if (!dispatched) return null;
152056
+ if (dispatched.status !== "accepted") return dispatched;
152057
+ accepted.onAccepted?.();
151638
152058
  topicRoot?.confirmConsumed();
151639
152059
  await deps.conclusions.owe(
151640
152060
  { chatKey, threadId, userMessageId, recordedAt: (/* @__PURE__ */ new Date()).toISOString() },
@@ -151658,11 +152078,12 @@ var makeTurns = (core, deps) => {
151658
152078
  binding.reply
151659
152079
  );
151660
152080
  });
151661
- return threadId;
152081
+ return { status: "started" };
151662
152082
  };
151663
152083
  const receiveAttachmentsOnly = async (binding, message, enteredWaiting) => {
151664
- const threadId = await ensureThread(binding, message);
151665
- if (threadId === null) return;
152084
+ const prepared = await ensureThread(binding, message);
152085
+ if (typeof prepared !== "string") return prepared;
152086
+ const threadId = prepared;
151666
152087
  const waiting = pendingFor(chatKeyOf(binding), threadId);
151667
152088
  const collected = await collectResources(
151668
152089
  binding,
@@ -151674,7 +152095,7 @@ var makeTurns = (core, deps) => {
151674
152095
  waiting.attachments.push(...collected.attachments);
151675
152096
  waiting.notes.push(...collected.notes.slice(0, MAX_ATTACHMENTS_PER_TURN));
151676
152097
  await enteredWaiting?.();
151677
- if (collected.attachments.length === 0) return;
152098
+ if (collected.attachments.length === 0) return { status: "settled" };
151678
152099
  await deliver(
151679
152100
  binding,
151680
152101
  t("reply.attachmentReceived", {
@@ -151682,6 +152103,7 @@ var makeTurns = (core, deps) => {
151682
152103
  count: waiting.attachments.length
151683
152104
  })
151684
152105
  );
152106
+ return { status: "settled" };
151685
152107
  };
151686
152108
  const hasWaitingAttachments = (chatKey) => (waitingFor(chatKey, chatOf(core.state, chatKey).threadId)?.attachments.length ?? 0) > 0;
151687
152109
  return {
@@ -155175,7 +155597,7 @@ var makeWecomAppAdapter = (deps) => {
155175
155597
  // src/index.ts
155176
155598
  var definition = {
155177
155599
  id: "im",
155178
- apiVersion: 27,
155600
+ apiVersion: 28,
155179
155601
  // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
155180
155602
  activate: async (ctx, testing = {}) => {
155181
155603
  const { dispatchSleep } = testing;
@@ -155276,7 +155698,7 @@ var definition = {
155276
155698
  t,
155277
155699
  adapters
155278
155700
  });
155279
- const acknowledgements = makeAcknowledgements(core);
155701
+ const acknowledgements = makeAcknowledgements(core, ctx.interval);
155280
155702
  const failures = makeFailureNotices(core);
155281
155703
  const replyAnchors = makeReplyAnchors(core.host);
155282
155704
  const conclusions = makeConclusionLedger(core);
@@ -155291,6 +155713,7 @@ var definition = {
155291
155713
  const teams = makeTeamGoals(core);
155292
155714
  const notifications = makeNotifications(core, {
155293
155715
  takeWorking: acknowledgements.takeWorking,
155716
+ stopTypingFor: acknowledgements.stopTypingFor,
155294
155717
  replyCards,
155295
155718
  failures,
155296
155719
  conclusions,
@@ -155330,6 +155753,7 @@ var definition = {
155330
155753
  });
155331
155754
  const chatSetup = makeChatSetup(core, {
155332
155755
  rows: projectRows,
155756
+ enqueueForChat: chatQueue.enqueueForChat,
155333
155757
  resumeMessage: (bot, message) => inbound.resumeMessage(bot, message),
155334
155758
  openPicker: (chatKey, rows, reply) => panelWiring.panel.openProjects(chatKey, rows, reply)
155335
155759
  });
@@ -155380,6 +155804,7 @@ var definition = {
155380
155804
  await inbound.stop();
155381
155805
  turns.stop();
155382
155806
  await chatQueue.closeAndDrain();
155807
+ await acknowledgements.stopDeferred();
155383
155808
  }
155384
155809
  });
155385
155810
  registerSettingsRowActions(