@ycodium-ai/plugin-im 0.2.3244 → 0.2.3252

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",
@@ -133389,7 +133557,11 @@ var PlanCommandMode = Literals(["toggle", "on", "off", "invalid"]);
133389
133557
  var ParsedChatCommand = Union2([
133390
133558
  SimpleChatCommand,
133391
133559
  Struct({ kind: Literal2("sessions"), arg: optionalKey2(String4) }),
133392
- Struct({ kind: Literals(["resume", "model", "repo"]), arg: String4 }),
133560
+ Struct({
133561
+ kind: Literals(["resume", "model", "repo", "version"]),
133562
+ arg: String4
133563
+ }),
133564
+ Struct({ kind: Literal2("history"), count: NullOr(Number5) }),
133393
133565
  Struct({
133394
133566
  kind: Literal2("preset"),
133395
133567
  arg: String4,
@@ -133434,6 +133606,8 @@ var parseChatCommand = (input) => {
133434
133606
  if (/^\/stop$/i.test(trimmed)) return { kind: "stop" };
133435
133607
  if (/^\/continue$/i.test(trimmed)) return { kind: "continue" };
133436
133608
  if (/^\/status$/i.test(trimmed)) return { kind: "status" };
133609
+ const versionArg = trimmed.match(/^\/version(?:\s+([\s\S]*))?$/iu);
133610
+ if (versionArg !== null) return { kind: "version", arg: (versionArg[1] ?? "").trim() };
133437
133611
  if (/^\/new$/i.test(trimmed)) return { kind: "new" };
133438
133612
  if (/^\/help$/i.test(trimmed)) return { kind: "help" };
133439
133613
  if (/^\/panel$/i.test(trimmed)) return { kind: "panel" };
@@ -133441,6 +133615,16 @@ var parseChatCommand = (input) => {
133441
133615
  if (sessions !== void 0) {
133442
133616
  return sessions === "" ? { kind: "sessions" } : { kind: "sessions", arg: sessions };
133443
133617
  }
133618
+ const history = commandArgument(
133619
+ trimmed,
133620
+ input.chinese,
133621
+ /^\/history(?:\s+([\s\S]*))?$/i,
133622
+ /^会话历史(?:\s+([\s\S]*))?$/
133623
+ );
133624
+ if (history !== void 0) {
133625
+ const count = history === "" ? HISTORY_COUNTS.defaultCount : /^\d+$/.test(history) ? Math.min(Number(history), HISTORY_COUNTS.maxCount) : 0;
133626
+ return { kind: "history", count: count > 0 ? count : null };
133627
+ }
133444
133628
  const resume = resumeArgument(input.text, input.chinese);
133445
133629
  if (resume !== void 0) {
133446
133630
  return resume === "" ? { kind: "sessions" } : { kind: "resume", arg: resume };
@@ -133477,9 +133661,11 @@ var helpText = (t, options = {}) => [
133477
133661
  t("help.stop"),
133478
133662
  t("help.continue"),
133479
133663
  t("help.status"),
133664
+ t("help.version"),
133480
133665
  t("help.new"),
133481
133666
  t("help.sessions"),
133482
133667
  t("help.resume"),
133668
+ t("help.history", HISTORY_COUNTS),
133483
133669
  t("help.syncNote"),
133484
133670
  t("help.model"),
133485
133671
  t("help.preset"),
@@ -133492,6 +133678,267 @@ var helpText = (t, options = {}) => [
133492
133678
  ...options.panel === true ? ["", t("help.panel")] : []
133493
133679
  ].join("\n");
133494
133680
 
133681
+ // src/deadline.ts
133682
+ var withDeadline = async (timeoutMs, load) => {
133683
+ const signal = AbortSignal.timeout(timeoutMs);
133684
+ let onAbort = () => {
133685
+ };
133686
+ const aborted = new Promise((_, reject) => {
133687
+ onAbort = () => reject(signal.reason);
133688
+ signal.addEventListener("abort", onAbort, { once: true });
133689
+ });
133690
+ try {
133691
+ return await Promise.race([load(signal), aborted]);
133692
+ } finally {
133693
+ signal.removeEventListener("abort", onAbort);
133694
+ }
133695
+ };
133696
+
133697
+ // src/threadMessages.ts
133698
+ import {
133699
+ isThreadNotFound,
133700
+ threadMessagesNewestFirst
133701
+ } from "@ycodium-ai/plugin-api";
133702
+ var PAGE_SIZE = 20;
133703
+ async function* newestFirst(threads, threadId) {
133704
+ try {
133705
+ yield* threadMessagesNewestFirst(threads, threadId, { pageSize: PAGE_SIZE });
133706
+ } catch (error) {
133707
+ if (!isThreadNotFound(error)) throw error;
133708
+ }
133709
+ }
133710
+ async function attachmentsOf(threads, threadId, message) {
133711
+ const collected = [...message.attachments ?? []];
133712
+ const total = message.attachmentsTotal ?? collected.length;
133713
+ while (collected.length < total) {
133714
+ let page;
133715
+ try {
133716
+ page = await threads.messages.list({
133717
+ threadId,
133718
+ messageId: message.id,
133719
+ attachmentOffset: collected.length,
133720
+ textLimit: 2
133721
+ });
133722
+ } catch (error) {
133723
+ if (isThreadNotFound(error)) break;
133724
+ throw error;
133725
+ }
133726
+ const more = page.items[0]?.attachments ?? [];
133727
+ if (more.length === 0) break;
133728
+ collected.push(...more);
133729
+ }
133730
+ return collected;
133731
+ }
133732
+ async function latestMessage(threads, threadId, accept) {
133733
+ for await (const message of newestFirst(threads, threadId)) {
133734
+ if (accept(message)) return message;
133735
+ }
133736
+ return void 0;
133737
+ }
133738
+ async function recentMessages(threads, threadId, count, accept) {
133739
+ const collected = [];
133740
+ for await (const message of newestFirst(threads, threadId)) {
133741
+ if (!accept(message)) continue;
133742
+ collected.push(message);
133743
+ if (collected.length >= count) break;
133744
+ }
133745
+ return collected.toReversed();
133746
+ }
133747
+ async function messagesAfterLastUser(threads, threadId) {
133748
+ const after = [];
133749
+ for await (const message of newestFirst(threads, threadId)) {
133750
+ if (message.role === "user") break;
133751
+ after.push(message);
133752
+ }
133753
+ return after.toReversed();
133754
+ }
133755
+
133756
+ // src/historyCommand.ts
133757
+ var READ_TIMEOUT_MS = 1e4;
133758
+ var MAX_PAGES = 3;
133759
+ var TEXT_LIMIT = 1e3;
133760
+ var makeHistoryCommand = (core) => {
133761
+ const { host, t, deliver } = core;
133762
+ return async (binding, command, chatKind) => {
133763
+ if (chatKind !== "private") {
133764
+ await deliver(binding, t("history.directOnly"));
133765
+ return;
133766
+ }
133767
+ const count = command.count;
133768
+ if (count === null) {
133769
+ await deliver(binding, t("history.usage", HISTORY_COUNTS));
133770
+ return;
133771
+ }
133772
+ const chatKey = chatKeyOf(binding);
133773
+ const threadId = chatOf(core.state, chatKey).threadId;
133774
+ if (threadId === null) {
133775
+ await deliver(binding, t("history.noSession"));
133776
+ return;
133777
+ }
133778
+ const switched = () => {
133779
+ if (chatOf(core.state, chatKey).threadId === threadId) return false;
133780
+ host.log.info("chat cancelled a history read after the session changed", { threadId });
133781
+ return true;
133782
+ };
133783
+ const adapter = core.adapterFor(binding.platform);
133784
+ if (adapter?.sendTyping !== void 0) {
133785
+ void adapter.sendTyping(binding).catch(() => {
133786
+ host.log.info("chat could not show history typing", { threadId });
133787
+ });
133788
+ }
133789
+ let text3;
133790
+ try {
133791
+ text3 = await withDeadline(READ_TIMEOUT_MS, async (signal) => {
133792
+ const threads = {
133793
+ messages: {
133794
+ list: async (input) => {
133795
+ signal.throwIfAborted();
133796
+ const page = await host.threads.messages.list({
133797
+ ...input,
133798
+ textLimit: input.textLimit ?? TEXT_LIMIT
133799
+ });
133800
+ signal.throwIfAborted();
133801
+ return page;
133802
+ }
133803
+ }
133804
+ };
133805
+ let pages = 0;
133806
+ let reachedEnd = false;
133807
+ const bounded = {
133808
+ messages: {
133809
+ list: async (input) => {
133810
+ const page = await threads.messages.list(input);
133811
+ pages += 1;
133812
+ reachedEnd = page.nextCursor === null;
133813
+ return { ...page, nextCursor: pages >= MAX_PAGES ? null : page.nextCursor };
133814
+ }
133815
+ }
133816
+ };
133817
+ const messages = await recentMessages(
133818
+ bounded,
133819
+ threadId,
133820
+ count,
133821
+ (message) => message.role === "user" || message.role === "assistant" && !message.streaming
133822
+ );
133823
+ if (messages.length === 0) return t(reachedEnd ? "history.empty" : "history.noneRecent");
133824
+ const entries = [];
133825
+ for (const message of messages) {
133826
+ const attachments = await attachmentsOf(threads, threadId, message);
133827
+ const body = [
133828
+ message.text.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "").trim(),
133829
+ ...message.textWindow?.nextOffset != null ? [t("history.truncated")] : [],
133830
+ ...attachments.map(
133831
+ (attachment) => t(attachment.type === "image" ? "history.image" : "history.file")
133832
+ )
133833
+ ].filter(Boolean).join("\n") || t("history.noText");
133834
+ const role = t(message.role === "user" ? "history.user" : "history.assistant");
133835
+ entries.push(`${entries.length + 1}. ${role}
133836
+ ${body}`);
133837
+ }
133838
+ return [t("history.title", { count: messages.length }), ...entries].join("\n\n");
133839
+ });
133840
+ } catch {
133841
+ if (switched()) return;
133842
+ host.log.warn("chat could not read history", { threadId });
133843
+ text3 = t("history.failed");
133844
+ }
133845
+ if (switched()) return;
133846
+ await deliver(binding, text3);
133847
+ };
133848
+ };
133849
+
133850
+ // package.json
133851
+ var package_default = {
133852
+ name: "@ycodium-ai/plugin-im",
133853
+ version: "0.2.3252",
133854
+ description: "Talk to your agents from Telegram, WeCom, Lark, or WeChat (experimental).",
133855
+ keywords: [
133856
+ "im",
133857
+ "lark",
133858
+ "telegram",
133859
+ "wechat",
133860
+ "wecom",
133861
+ "wecom-app",
133862
+ "ycodium",
133863
+ "ycodium-plugin"
133864
+ ],
133865
+ license: "UNLICENSED",
133866
+ files: [
133867
+ "dist",
133868
+ "assets",
133869
+ "locales"
133870
+ ],
133871
+ type: "module",
133872
+ publishConfig: {
133873
+ access: "public"
133874
+ },
133875
+ scripts: {
133876
+ build: "node build.mjs",
133877
+ prepack: "pnpm run build",
133878
+ typecheck: "tsgo --noEmit && tsgo --noEmit -p tsconfig.ui.json",
133879
+ test: "vp test run"
133880
+ },
133881
+ devDependencies: {
133882
+ "@effect/vitest": "catalog:",
133883
+ "@larksuiteoapi/node-sdk": "^1.73.0",
133884
+ "@types/markdown-it": "^14.2.0",
133885
+ "@types/react": "~19.2.14",
133886
+ "@ycodium-ai/plugin-api": "workspace:*",
133887
+ effect: "catalog:",
133888
+ esbuild: "catalog:",
133889
+ "markdown-it": "^15.0.2",
133890
+ react: "19.2.6",
133891
+ "vite-plus": "catalog:",
133892
+ "ycodium-react-test-harness": "workspace:*"
133893
+ },
133894
+ ycodium: {
133895
+ plugin: "./dist/index.js",
133896
+ displayName: "Chat bridge",
133897
+ description: "Talk to your agents from Telegram, WeCom, Lark, or WeChat (experimental).",
133898
+ icon: "messageCircle",
133899
+ permissions: [
133900
+ {
133901
+ kind: "sessionProjection"
133902
+ },
133903
+ {
133904
+ kind: "threads.user"
133905
+ },
133906
+ {
133907
+ kind: "workspace.read"
133908
+ },
133909
+ {
133910
+ kind: "commands.dispatch",
133911
+ commands: [
133912
+ "thread.create",
133913
+ "thread.meta.update",
133914
+ "thread.external-messages.append",
133915
+ "thread.turn.start",
133916
+ "thread.turn.interrupt",
133917
+ "thread.approval.respond",
133918
+ "thread.user-input.respond",
133919
+ "thread.turn-intent.run",
133920
+ "thread.turn-intent.cancel",
133921
+ "thread.turn-queue.resume"
133922
+ ]
133923
+ },
133924
+ {
133925
+ kind: "ui.seats",
133926
+ seats: [
133927
+ "settings.section"
133928
+ ]
133929
+ }
133930
+ ],
133931
+ ui: {
133932
+ assets: "./assets",
133933
+ entry: "ui.js"
133934
+ }
133935
+ }
133936
+ };
133937
+
133938
+ // src/versionCommand.ts
133939
+ var pluginVersion = package_default.version;
133940
+ var versionReply = (t, arg) => arg === "" ? t("version.current", { version: pluginVersion }) : t("version.usage");
133941
+
133495
133942
  // src/commands.ts
133496
133943
  var NEEDS_PROJECT_COMMANDS = /* @__PURE__ */ new Set(["sessions", "resume", "selectTeam", "selectThread"]);
133497
133944
  var makeCommands = (core, deps) => {
@@ -133501,8 +133948,9 @@ var makeCommands = (core, deps) => {
133501
133948
  const { showSessions, resumeSession } = deps.sessions;
133502
133949
  const { handleModel, handlePlan } = deps.modelPlan;
133503
133950
  const { openPanel } = deps;
133951
+ const handleHistory = makeHistoryCommand(core);
133504
133952
  const targetOf = (binding) => chatOf(core.state, chatKeyOf(binding)).target;
133505
- const executeCommand = async (binding, command, senderId = null) => {
133953
+ const executeCommand = async (binding, command, senderId = null, chatKind = void 0) => {
133506
133954
  const chatKey = chatKeyOf(binding);
133507
133955
  host.log.info("chat handled a command", {
133508
133956
  bot: binding.displayName,
@@ -133553,6 +134001,9 @@ var makeCommands = (core, deps) => {
133553
134001
  case "sessions":
133554
134002
  await showSessions(binding, command.arg);
133555
134003
  return;
134004
+ case "history":
134005
+ await handleHistory(binding, command, chatKind);
134006
+ return;
133556
134007
  case "resume":
133557
134008
  await resumeSession(binding, command.arg);
133558
134009
  return;
@@ -133569,6 +134020,9 @@ var makeCommands = (core, deps) => {
133569
134020
  bindChat(chatKey, null);
133570
134021
  await deliver(binding, t("command.newSession"));
133571
134022
  return;
134023
+ case "version":
134024
+ await deliver(binding, versionReply(t, command.arg));
134025
+ return;
133572
134026
  case "status": {
133573
134027
  const threadId = chatOf(core.state, chatKey).threadId ?? void 0;
133574
134028
  const routed = targetOf(binding);
@@ -135185,56 +135639,6 @@ var deliveryFailure = (core, binding, threadId, turnId) => async (error) => {
135185
135639
  }
135186
135640
  };
135187
135641
 
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
135642
  // src/conclusionDelivery.ts
135239
135643
  var ALWAYS_OWED = () => true;
135240
135644
  var makeConclusionDelivery = (core, anchors) => {
@@ -135604,16 +136008,6 @@ var workForMessage = (message) => ({
135604
136008
  });
135605
136009
 
135606
136010
  // 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
136011
  var executeInboundRoute = async (core, actions, input) => {
135618
136012
  const { current, message, route, work } = input;
135619
136013
  const target2 = input.target;
@@ -135623,82 +136017,113 @@ var executeInboundRoute = async (core, actions, input) => {
135623
136017
  switch (route.kind) {
135624
136018
  case "command":
135625
136019
  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({
136020
+ await actions.commands.executeCommand(
136021
+ target2,
136022
+ route.command,
136023
+ message.senderId,
136024
+ message.chatKind
136025
+ );
136026
+ return { status: "settled" };
136027
+ case "project": {
136028
+ if (route.decision.kind !== "auto") {
136029
+ await complete();
136030
+ await actions.chatSetup.executeProjectDecision(target2, message, route.decision);
136031
+ return { status: "settled" };
136032
+ }
136033
+ const replies = [];
136034
+ const respond = async () => {
136035
+ for (const reply of replies) await reply();
136036
+ };
136037
+ if (await actions.chatSetup.executeProjectDecision(
136038
+ target2,
136039
+ message,
136040
+ route.decision,
136041
+ (reply) => replies.push(reply)
136042
+ )) {
136043
+ const outcome = await actions.continueMessage({
135633
136044
  ...core.bindingOf(current, message.chatId),
135634
136045
  reply: target2.reply
135635
136046
  });
135636
- } else {
135637
- if (route.decision.kind === "auto") await complete();
135638
- await settleInbound(core, target2, message.messageId);
136047
+ if (outcome.status === "settled")
136048
+ return {
136049
+ status: "settled",
136050
+ respond: async () => {
136051
+ await respond();
136052
+ await outcome.respond?.();
136053
+ }
136054
+ };
136055
+ await respond();
136056
+ return outcome;
135639
136057
  }
135640
- return;
136058
+ await complete();
136059
+ return { status: "settled", respond };
136060
+ }
135641
136061
  case "question":
135642
136062
  await complete();
135643
136063
  await actions.interactions.questionFlow.answerText(route.answer);
135644
- await settleInbound(core, target2, message.messageId);
135645
- return;
136064
+ return { status: "settled" };
135646
136065
  case "approval":
135647
136066
  await complete();
135648
136067
  await actions.approvals.answerByToken(route.token);
135649
- await settleInbound(core, target2, message.messageId);
135650
- return;
136068
+ return { status: "settled" };
135651
136069
  case "unavailable":
135652
136070
  await complete();
135653
- await settleInbound(core, target2, message.messageId);
135654
- return;
135655
- case "attachments":
135656
- await actions.turns.receiveAttachmentsOnly(target2, message, complete);
136071
+ return { status: "settled" };
136072
+ case "attachments": {
136073
+ const outcome = await actions.turns.receiveAttachmentsOnly(target2, message, complete);
135657
136074
  await complete();
135658
- await settleInbound(core, target2, message.messageId);
135659
- return;
136075
+ return outcome;
136076
+ }
135660
136077
  case "team": {
135661
136078
  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(
136079
+ const outcome = await actions.teams.startTeamGoal(
135665
136080
  routed,
135666
136081
  message,
135667
- actions.acceptedWork(routed, message, work)
136082
+ actions.acceptedWork(routed, message, work, input.onAccepted)
135668
136083
  );
135669
- if (leadThreadId === null) {
136084
+ if (outcome.status === "settled") {
135670
136085
  await complete();
135671
- await settleInbound(core, target2, message.messageId);
136086
+ return {
136087
+ status: "settled",
136088
+ respond: async () => {
136089
+ if (message.resources.length > 0)
136090
+ await core.deliver(routed, core.t("reply.attachmentsNoTeam"));
136091
+ await outcome.respond?.();
136092
+ }
136093
+ };
135672
136094
  }
135673
- return;
136095
+ if (message.resources.length > 0)
136096
+ await core.deliver(routed, core.t("reply.attachmentsNoTeam"));
136097
+ return outcome;
135674
136098
  }
135675
136099
  case "turn":
135676
- await actions.openTurn(
136100
+ return actions.openTurn(
135677
136101
  { ...target2, chatId: route.chatId, projectId: route.projectId },
135678
136102
  message,
135679
- work
136103
+ work,
136104
+ input.onAccepted
135680
136105
  );
135681
- return;
135682
136106
  case "handoff": {
135683
- const executeHandoff = async () => {
136107
+ const executeHandoff = async (onAccepted) => {
135684
136108
  actions.inheritFromParent(current, route.chatId, message.chatId);
135685
136109
  const child = {
135686
136110
  ...core.bindingOf(current, route.chatId),
135687
136111
  projectId: route.projectId,
135688
136112
  reply: target2.reply
135689
136113
  };
135690
- await actions.openTurn(child, message, work);
136114
+ return actions.openTurn(child, message, work, onAccepted);
135691
136115
  };
135692
136116
  if (input.queueChatId === route.chatId) {
135693
- await executeHandoff();
135694
- return;
136117
+ return executeHandoff(input.onAccepted);
135695
136118
  }
135696
- actions.enqueueForChat(
135697
- chatKeyOf({ rowId: current.rowId, chatId: route.chatId }),
135698
- executeHandoff,
135699
- true
135700
- );
135701
- return;
136119
+ return {
136120
+ status: "deferred",
136121
+ resume: () => actions.enqueueForChat(
136122
+ chatKeyOf({ rowId: current.rowId, chatId: route.chatId }),
136123
+ () => actions.run(executeHandoff),
136124
+ true
136125
+ )
136126
+ };
135702
136127
  }
135703
136128
  }
135704
136129
  };
@@ -135875,16 +136300,14 @@ var makeInbound = (core, deps) => {
135875
136300
  }));
135876
136301
  return true;
135877
136302
  };
135878
- const turnAwayStranger = (target2, sender) => {
136303
+ const turnAwayStranger = async (target2, sender) => {
135879
136304
  host.log.warn("chat ignored a message from someone not on the allow list", {
135880
136305
  bot: target2.displayName,
135881
136306
  sender
135882
136307
  });
135883
136308
  if (sender === null) return;
135884
136309
  if (!notAllowedReplyCooldown.tryAcquire(`${target2.rowId}:${sender}`)) return;
135885
- enqueueForChat(chatKeyOf(target2), async () => {
135886
- await deliver(target2, t("reply.notAllowedYet", { id: sender }));
135887
- });
136310
+ await deliver(target2, t("reply.notAllowedYet", { id: sender }));
135888
136311
  };
135889
136312
  const currentBot = (rowId) => bots().find((candidate) => candidate.rowId === rowId);
135890
136313
  const inheritFromParent = (bot, chatId, parentChatId) => {
@@ -135911,24 +136334,24 @@ var makeInbound = (core, deps) => {
135911
136334
  if (known === void 0) noteChatAlias(bot, message.chatAlias, message.chatId);
135912
136335
  return known === void 0 || known === message.chatId ? message : { ...message, chatId: known };
135913
136336
  };
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
- };
136337
+ const acceptedWork = (speaker, message, work, onAccepted) => Object.assign(
136338
+ async (threadId, register) => {
136339
+ await markWorking(speaker, threadId, message.messageId);
136340
+ await register?.();
136341
+ if (work !== void 0) await inboundState.complete(speaker.rowId, work.key);
136342
+ },
136343
+ { onAccepted }
136344
+ );
136345
+ const openTurn = (speaker, message, work, onAccepted) => startTurn(
136346
+ speaker,
136347
+ message,
136348
+ acceptedWork(speaker, message, work, onAccepted),
136349
+ work === void 0 ? void 0 : processingFor(inboundState, speaker.rowId, work, () => {
136350
+ replay.enqueue(speaker.rowId, work.key, Promise.resolve(true));
136351
+ })
136352
+ );
135929
136353
  const resumeMessage = async (current, message, work, queueChatId = message.chatId) => {
135930
- inheritFromParent(current, message.chatId, message.parentChatId);
135931
- const target2 = {
136354
+ let target2 = {
135932
136355
  ...bindingOf2(current, message.chatId),
135933
136356
  reply: anchorOfMessage(adapterFor(current.platform), message)
135934
136357
  };
@@ -135939,14 +136362,24 @@ var makeInbound = (core, deps) => {
135939
136362
  const complete = async () => {
135940
136363
  if (work !== void 0) await inboundState.complete(current.rowId, work.key);
135941
136364
  };
136365
+ const identity2 = {
136366
+ platform: current.platform,
136367
+ rowId: current.rowId,
136368
+ chatId: message.chatId,
136369
+ messageId: message.messageId
136370
+ };
136371
+ const run2 = (execute2) => runInbound(core, target2, message.messageId, execute2, {
136372
+ rememberDeferred: () => deps.acknowledgements.rememberDeferred(identity2, target2),
136373
+ clearDeferred: () => deps.acknowledgements.clearDeferred(identity2),
136374
+ complete
136375
+ });
135942
136376
  const continueMessage = async (target3) => {
135943
136377
  const boundThread = chatOf(core.state, chatKey).threadId;
135944
136378
  const answer = boundThread === null ? void 0 : questionFlow.textAnswer(chatKey, boundThread, message.text);
135945
136379
  if (answer !== void 0) {
135946
136380
  const route2 = { kind: "question", answer };
135947
136381
  await saveRoute(route2);
135948
- await execute(route2, target3);
135949
- return;
136382
+ return execute(route2, target3);
135950
136383
  }
135951
136384
  const project = await projectOrNull(host.projects, target3.projectId);
135952
136385
  if (project === null || project.archivedAt != null) {
@@ -135958,14 +136391,16 @@ var makeInbound = (core, deps) => {
135958
136391
  projectId: target3.projectId
135959
136392
  });
135960
136393
  }
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;
136394
+ return {
136395
+ status: "settled",
136396
+ respond: async () => {
136397
+ if (archivedReplyCooldown.tryAcquire(chatKey))
136398
+ await deliver(
136399
+ target3,
136400
+ project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
136401
+ );
136402
+ }
136403
+ };
135969
136404
  }
135970
136405
  const routed = targetOf(target3);
135971
136406
  const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
@@ -135973,20 +136408,21 @@ var makeInbound = (core, deps) => {
135973
136408
  if (attachmentsOnly) {
135974
136409
  await saveRoute({ kind: "unavailable" });
135975
136410
  await complete();
135976
- await deliver(target3, t("reply.attachmentsNoTeam"));
135977
- await settleInbound(core, target3, message.messageId);
135978
- return;
136411
+ return {
136412
+ status: "settled",
136413
+ respond: async () => {
136414
+ await deliver(target3, t("reply.attachmentsNoTeam"));
136415
+ }
136416
+ };
135979
136417
  }
135980
136418
  const route2 = { kind: "team", chatId: target3.chatId, projectId: target3.projectId };
135981
136419
  await saveRoute(route2);
135982
- await execute(route2, target3);
135983
- return;
136420
+ return execute(route2, target3);
135984
136421
  }
135985
136422
  if (attachmentsOnly) {
135986
136423
  const route2 = { kind: "attachments" };
135987
136424
  await saveRoute(route2);
135988
- await execute(route2, target3);
135989
- return;
136425
+ return execute(route2, target3);
135990
136426
  }
135991
136427
  const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
135992
136428
  const route = {
@@ -135995,62 +136431,65 @@ var makeInbound = (core, deps) => {
135995
136431
  projectId: target3.projectId
135996
136432
  };
135997
136433
  await saveRoute(route);
135998
- await execute(route, target3);
136434
+ return execute(route, target3);
135999
136435
  };
136000
136436
  const execute = (route, routedTarget = target2) => executeInboundRoute(
136001
136437
  core,
136002
- { ...deps, openTurn, acceptedWork, inheritFromParent, continueMessage },
136003
- { current, message, target: routedTarget, route, work, queueChatId }
136438
+ { ...deps, openTurn, acceptedWork, inheritFromParent, continueMessage, run: run2 },
136439
+ { current, message, target: routedTarget, route, work, queueChatId, onAccepted }
136004
136440
  );
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);
136441
+ let onAccepted;
136442
+ const executeMessage = async (accepted) => {
136443
+ onAccepted = accepted;
136444
+ inheritFromParent(current, message.chatId, message.parentChatId);
136445
+ target2 = { ...bindingOf2(current, message.chatId), reply: target2.reply };
136446
+ if (work?.route !== void 0) return execute(work.route);
136447
+ if (!isAllowedSender(target2, message.senderId, message.chatKind)) {
136448
+ await saveRoute({ kind: "unavailable" });
136449
+ await complete();
136450
+ return { status: "settled", respond: () => turnAwayStranger(target2, message.senderId) };
136451
+ }
136452
+ const unsupportedType = message.unsupportedType;
136453
+ if (unsupportedType !== void 0) {
136454
+ await saveRoute({ kind: "unavailable" });
136455
+ await complete();
136456
+ return {
136457
+ status: "settled",
136458
+ respond: async () => {
136459
+ if (unsupportedReplyCooldown.tryAcquire(chatKey))
136460
+ await deliver(
136461
+ target2,
136462
+ unsupportedType === "folder" ? t("reply.unsupportedFolder") : unsupportedType === "voice" ? t("reply.unsupportedVoice") : t("reply.unsupportedType", { type: unsupportedType })
136463
+ );
136464
+ }
136465
+ };
136466
+ }
136467
+ const approval = openApprovalOf(core.state, chatKey);
136468
+ const token = approval === void 0 ? null : adapterFor(target2.platform)?.answerByText?.(target2, message.text, approval.options) ?? null;
136469
+ if (token !== null) {
136470
+ const route = { kind: "approval", token };
136471
+ await saveRoute(route);
136472
+ return execute(route);
136473
+ }
136474
+ const command = parseChatCommand({
136475
+ text: message.text,
136476
+ chinese: t.language === "zh-CN",
136477
+ hasImages: message.resources.length > 0
136478
+ });
136479
+ if (command.kind !== "message") {
136480
+ const route = { kind: "command", command };
136481
+ await saveRoute(route);
136482
+ return execute(route);
136483
+ }
136484
+ if (target2.projectId === "") {
136485
+ const decision = await decideProjectMessage(target2, message);
136486
+ const route = { kind: "project", decision };
136487
+ await saveRoute(route);
136488
+ return execute(route);
136489
+ }
136490
+ return continueMessage(target2);
136491
+ };
136492
+ await run2(executeMessage);
136054
136493
  };
136055
136494
  const inboundHandlersFor = (bot) => ({
136056
136495
  onError: (message) => host.log.warn("chat inbound connection failed", {
@@ -136137,7 +136576,10 @@ var makeInbound = (core, deps) => {
136137
136576
  inboundHandlersFor,
136138
136577
  resumeMessage,
136139
136578
  enqueueReplay,
136140
- cancelReplay: replay.cancelReplay,
136579
+ cancelReplay: (rowId) => {
136580
+ replay.cancelReplay(rowId);
136581
+ track(deps.acknowledgements.stopDeferred(rowId));
136582
+ },
136141
136583
  stop: async () => {
136142
136584
  accepting = false;
136143
136585
  replay.stop();
@@ -137410,7 +137852,7 @@ var ConsoleError = class extends Error {
137410
137852
  this.retryAfterMs = extra.retryAfterMs;
137411
137853
  }
137412
137854
  };
137413
- var withDeadline = async (deps, ms, run2) => {
137855
+ var withDeadline2 = async (deps, ms, run2) => {
137414
137856
  const controller = new AbortController();
137415
137857
  const timer = new AbortController();
137416
137858
  let timedOut = false;
@@ -137437,7 +137879,7 @@ var withDeadline = async (deps, ms, run2) => {
137437
137879
  deps.signal.removeEventListener("abort", onParentAbort);
137438
137880
  }
137439
137881
  };
137440
- var guarded = withDeadline;
137882
+ var guarded = withDeadline2;
137441
137883
  var sleepOrAbort = async (deps, ms) => {
137442
137884
  try {
137443
137885
  await deps.clock.sleep(ms, deps.signal);
@@ -137469,7 +137911,7 @@ var retryAfterMs = (response) => {
137469
137911
  };
137470
137912
  var openConsole = async (deps, jar) => {
137471
137913
  const pageUrl = `${CONSOLE_ORIGIN}${CONSOLE_APP_PATH}`;
137472
- const loadPage = () => withDeadline(deps, TIMEOUT_MS.page, async (signal) => {
137914
+ const loadPage = () => withDeadline2(deps, TIMEOUT_MS.page, async (signal) => {
137473
137915
  const { response, finalUrl } = await jar.fetch(deps.fetch, pageUrl, {
137474
137916
  method: "GET",
137475
137917
  headers: { accept: "text/html,application/xhtml+xml" },
@@ -137498,7 +137940,7 @@ var openConsole = async (deps, jar) => {
137498
137940
  calls += 1;
137499
137941
  const started = deps.clock.now();
137500
137942
  try {
137501
- const result2 = await withDeadline(deps, timeoutOf(kind), async (signal) => {
137943
+ const result2 = await withDeadline2(deps, timeoutOf(kind), async (signal) => {
137502
137944
  const { response } = await jar.fetch(deps.fetch, `${origin}${path}`, {
137503
137945
  method: "POST",
137504
137946
  headers: {
@@ -145068,22 +145510,6 @@ var larkInboundPersistence = (contextFor) => ({
145068
145510
  }
145069
145511
  });
145070
145512
 
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
145513
  // src/boundedSeen.ts
145088
145514
  var makeBoundedSeen = (limit) => {
145089
145515
  const seen = /* @__PURE__ */ new Set();
@@ -145229,7 +145655,7 @@ var errorReason = (error) => {
145229
145655
  };
145230
145656
  var loadReplyReference = async (load) => {
145231
145657
  try {
145232
- return await withDeadline2(LOAD_TIMEOUT_MS, load) ?? { unavailableReason: "not-found" };
145658
+ return await withDeadline(LOAD_TIMEOUT_MS, load) ?? { unavailableReason: "not-found" };
145233
145659
  } catch (error) {
145234
145660
  return { unavailableReason: errorReason(error) };
145235
145661
  }
@@ -145248,7 +145674,7 @@ var makeLookup = (rowId, load) => {
145248
145674
  if (known !== void 0 && Date.now() - known.at < CACHE_MS) return known.value;
145249
145675
  const pending = inflight.get(key2);
145250
145676
  if (pending !== void 0) return pending;
145251
- const reading = withDeadline2(LOOKUP_TIMEOUT_MS, (signal) => load(id, signal)).catch(() => null).then((value2) => {
145677
+ const reading = withDeadline(LOOKUP_TIMEOUT_MS, (signal) => load(id, signal)).catch(() => null).then((value2) => {
145252
145678
  cache.set(key2, { value: value2, at: Date.now() });
145253
145679
  return value2;
145254
145680
  }).finally(() => inflight.delete(key2));
@@ -145428,7 +145854,7 @@ ${indent} `)}`;
145428
145854
  };
145429
145855
  var loadMergeForward = async (message, deps) => {
145430
145856
  try {
145431
- const items = await withDeadline2(1e4, async (signal) => {
145857
+ const items = await withDeadline(1e4, async (signal) => {
145432
145858
  const token = await deps.tenantToken();
145433
145859
  signal.throwIfAborted();
145434
145860
  return fetchMergeForwardMessages({
@@ -145848,6 +146274,8 @@ var en_default = {
145848
146274
  sendingFile: "Sending {{name}}:",
145849
146275
  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
146276
  received: "Got it, working on it.",
146277
+ inboundFailed: "This message couldn't be handled. Please send it again later.",
146278
+ inboundDeferred: "No need to send this again \u2014 it will be handled automatically the next time the machine starts.",
145851
146279
  welcome: "Connected. Send text or images to get started, or send /help to see the available commands."
145852
146280
  },
145853
146281
  approval: {
@@ -145924,17 +146352,23 @@ var en_default = {
145924
146352
  project: "Project: {{title}}",
145925
146353
  noProject: "No project picked yet. Send /repo to pick one."
145926
146354
  },
146355
+ version: {
146356
+ current: "Ycodium Chat bridge v{{version}}",
146357
+ usage: "Usage: /version (no arguments)."
146358
+ },
145927
146359
  help: {
145928
146360
  title: "Commands:",
145929
146361
  continue: "\xB7 /continue \u2014 resume sending after /stop or a failure paused it; the waiting messages go out",
145930
146362
  stop: "\xB7 /stop \u2014 stop the turn that is running; the session stays",
145931
146363
  status: "\xB7 /status \u2014 what is going on right now",
146364
+ version: "\xB7 /version \u2014 the chat bridge's version (no arguments)",
145932
146365
  new: "\xB7 /new \u2014 forget the current session; the next message starts a new one",
145933
146366
  team: "\xB7 /team \u2014 give this project's team a goal with each message; use /repo to pick the project first",
145934
146367
  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.",
146368
+ 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
146369
  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
146370
  resume: "\xB7 /resume number-or-id-prefix \u2014 switch sessions; one from another project switches the project too",
146371
+ history: "\xB7 /history [count] \u2014 preview recent messages in the current session, in private chats only (default {{defaultCount}}, maximum {{maxCount}})",
145938
146372
  model: "\xB7 /model \u2014 see the model this chat uses; /model number or /model instance/model switches it",
145939
146373
  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
146374
  plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
@@ -145943,6 +146377,21 @@ var en_default = {
145943
146377
  group: "\xB7 /group name \u2014 Lark only: the bot creates a group and adds you (bots on other platforms cannot)",
145944
146378
  syncNote: "\xB7 After a private chat attaches a session, web user text and final assistant text sync here (groups never sync)."
145945
146379
  },
146380
+ history: {
146381
+ usage: "Usage: /history [count] \u2014 1 to {{maxCount}} (larger numbers count as {{maxCount}}), default {{defaultCount}}.",
146382
+ directOnly: "In a group this would show the session to everyone. Send /history in a private chat with the bot.",
146383
+ 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.",
146384
+ empty: "No messages to show in this session yet.",
146385
+ noneRecent: "Nothing to show among this session's most recent messages (older ones were not checked).",
146386
+ failed: "Could not read the recent messages. Try again in a moment.",
146387
+ title: "Recent messages in this session ({{count}}):",
146388
+ user: "User",
146389
+ assistant: "Assistant",
146390
+ image: "[Image]",
146391
+ file: "[File]",
146392
+ noText: "[No previewable text]",
146393
+ truncated: "[Remaining text omitted]"
146394
+ },
145946
146395
  sessions: {
145947
146396
  title: "This project's sessions ({{count}}):",
145948
146397
  empty: "This bot has not opened any session in this project yet. Send a message to start one.",
@@ -146755,6 +147204,8 @@ var zh_CN_default = {
146755
147204
  sendingFile: "\u6B63\u5728\u53D1\u9001 {{name}}\uFF1A",
146756
147205
  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
147206
  received: "\u6536\u5230\uFF0C\u5728\u5904\u7406\u3002",
147207
+ inboundFailed: "\u8FD9\u6761\u6D88\u606F\u5904\u7406\u65F6\u51FA\u4E86\u9519\uFF0C\u8BF7\u7A0D\u540E\u91CD\u53D1\u3002",
147208
+ inboundDeferred: "\u4E0D\u7528\u91CD\u53D1\uFF1A\u673A\u5668\u4E0B\u6B21\u542F\u52A8\u65F6\u4F1A\u81EA\u52A8\u5904\u7406\u8FD9\u6761\u6D88\u606F\u3002",
146758
147209
  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
147210
  },
146760
147211
  approval: {
@@ -146831,17 +147282,23 @@ var zh_CN_default = {
146831
147282
  project: "\u9879\u76EE\uFF1A{{title}}",
146832
147283
  noProject: "\u8FD8\u6CA1\u9009\u9879\u76EE\u3002\u53D1 /repo \u9009\u4E00\u4E2A\u3002"
146833
147284
  },
147285
+ version: {
147286
+ current: "Ycodium \u804A\u5929\u6865 v{{version}}",
147287
+ usage: "\u7528\u6CD5\uFF1A/version\uFF08\u4E0D\u5E26\u53C2\u6570\uFF09\u3002"
147288
+ },
146834
147289
  help: {
146835
147290
  title: "\u80FD\u7528\u7684\u547D\u4EE4\uFF1A",
146836
147291
  continue: "\xB7 /continue \u2014\u2014 /stop \u6216\u51FA\u9519\u6682\u505C\u4E86\u53D1\u9001\u4E4B\u540E\uFF0C\u7528\u5B83\u6062\u590D\uFF0C\u6392\u7740\u7684\u6D88\u606F\u7EE7\u7EED\u53D1\u51FA",
146837
147292
  stop: "\xB7 /stop \u2014\u2014 \u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E\uFF0C\u4F1A\u8BDD\u7559\u7740",
146838
147293
  status: "\xB7 /status \u2014\u2014 \u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
147294
+ version: "\xB7 /version \u2014\u2014 \u804A\u5929\u6865\u7684\u7248\u672C\uFF08\u4E0D\u5E26\u53C2\u6570\uFF09",
146839
147295
  new: "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
146840
147296
  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
147297
  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",
147298
+ 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
147299
  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
147300
  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",
147301
+ 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
147302
  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
147303
  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
147304
  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 +147307,21 @@ var zh_CN_default = {
146850
147307
  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
147308
  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
147309
  },
147310
+ history: {
147311
+ 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",
147312
+ 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",
147313
+ 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",
147314
+ empty: "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u80FD\u67E5\u770B\u7684\u6D88\u606F\u3002",
147315
+ 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",
147316
+ failed: "\u6CA1\u80FD\u8BFB\u53D6\u6700\u8FD1\u7684\u6D88\u606F\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
147317
+ title: "\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1\u7684\u6D88\u606F\uFF08{{count}} \u6761\uFF09\uFF1A",
147318
+ user: "\u7528\u6237",
147319
+ assistant: "\u52A9\u624B",
147320
+ image: "[\u56FE\u7247]",
147321
+ file: "[\u6587\u4EF6]",
147322
+ noText: "[\u6CA1\u6709\u53EF\u9884\u89C8\u7684\u6587\u5B57]",
147323
+ truncated: "[\u5176\u4F59\u6587\u5B57\u5DF2\u7701\u7565]"
147324
+ },
146853
147325
  sessions: {
146854
147326
  title: "\u672C\u9879\u76EE\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
146855
147327
  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 +148076,7 @@ var CREATE_INTERVAL_MS = 1100;
147604
148076
  var syncLarkSlashCommands = async (input) => {
147605
148077
  const request = async (method, id, body) => {
147606
148078
  input.signal.throwIfAborted();
147607
- return withDeadline2(REQUEST_TIMEOUT_MS, async (timeout2) => {
148079
+ return withDeadline(REQUEST_TIMEOUT_MS, async (timeout2) => {
147608
148080
  const signal = AbortSignal.any([input.signal, timeout2]);
147609
148081
  const response = await input.http(
147610
148082
  id === void 0 ? ENDPOINT : `${ENDPOINT}/${encodeURIComponent(id)}`,
@@ -147681,7 +148153,7 @@ var makeLarkSlashCommands = (deps) => {
147681
148153
  const run2 = app.settled.then(async () => {
147682
148154
  if (signal.aborted) return;
147683
148155
  try {
147684
- const token = await withDeadline2(
148156
+ const token = await withDeadline(
147685
148157
  REQUEST_TIMEOUT_MS,
147686
148158
  (timeout2) => deps.tenantToken(bot, AbortSignal.any([signal, timeout2]))
147687
148159
  );
@@ -148159,14 +148631,14 @@ var makeLarkAdapter = (deps) => {
148159
148631
  }),
148160
148632
  clearAcknowledge: async (binding, messageId, ackId, outcome) => {
148161
148633
  const token = await tenantToken(binding);
148162
- await withDeadline2(
148634
+ await withDeadline(
148163
148635
  LARK_REACTION_TIMEOUT_MS,
148164
148636
  (signal) => removeReaction({ http: deps.http, token, messageId, reactionId: ackId, signal })
148165
148637
  );
148166
148638
  const emojiType = outcome === "done" ? LARK_DONE_REACTION : outcome === "failed" ? LARK_FAILED_REACTION : null;
148167
148639
  if (emojiType === null) return;
148168
148640
  try {
148169
- await withDeadline2(
148641
+ await withDeadline(
148170
148642
  LARK_REACTION_TIMEOUT_MS,
148171
148643
  (signal) => addReaction({ http: deps.http, token, messageId, emojiType, signal })
148172
148644
  );
@@ -148436,7 +148908,7 @@ var outcomeOfSession = (status) => {
148436
148908
  var THREAD_PROJECT_CACHE_LIMIT = 256;
148437
148909
  var makeNotifications = (core, deps) => {
148438
148910
  const { host, adapterFor, bindingForChatKey, ready } = core;
148439
- const { takeWorking } = deps;
148911
+ const { takeWorking, stopTypingFor } = deps;
148440
148912
  const { turnCards } = deps.replyCards;
148441
148913
  const { failures, conclusions, delivery } = deps;
148442
148914
  const { recoverConclusion, recoverConclusions } = makeConclusionRecovery(
@@ -148489,6 +148961,7 @@ var makeNotifications = (core, deps) => {
148489
148961
  const threadId = payload.threadId;
148490
148962
  if (typeof threadId !== "string") return;
148491
148963
  if (payload.session?.activeTurnId != null) return;
148964
+ stopTypingFor(threadId);
148492
148965
  const outcome = outcomeOfSession(payload.session?.status);
148493
148966
  const said = threadsWithSomethingToReport.delete(threadId);
148494
148967
  await ready;
@@ -148533,6 +149006,7 @@ var makeNotifications = (core, deps) => {
148533
149006
  host.events.on("thread.failed", async (event) => {
148534
149007
  const threadId = event.threadId;
148535
149008
  if (threadId === null) return;
149009
+ stopTypingFor(threadId);
148536
149010
  threadsWithSomethingToReport.delete(threadId);
148537
149011
  await ready;
148538
149012
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
@@ -150072,6 +150546,7 @@ var makeTeamGoals = (core) => {
150072
150546
  };
150073
150547
  const startTeamGoal = async (binding, message, accepted) => {
150074
150548
  let goal;
150549
+ let confirmConsumed;
150075
150550
  try {
150076
150551
  const requestId = teamGoalRequestId(binding, message.messageId);
150077
150552
  const topicRoot = await message.topicRootForSession?.(requestId);
@@ -150086,24 +150561,30 @@ var makeTeamGoals = (core) => {
150086
150561
  }
150087
150562
  });
150088
150563
  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;
150564
+ const key2 = result2.code === "no-roster" ? "team.noRoster" : result2.code === "invalid-goal" ? "team.invalidGoal" : "team.startFailed";
150565
+ return {
150566
+ status: "settled",
150567
+ respond: async () => {
150568
+ await deliver(binding, t(key2));
150569
+ }
150570
+ };
150096
150571
  }
150097
150572
  if (!isRecord3(result2) || typeof result2.leadThreadId !== "string" || result2.leadThreadId === "" || result2.leadName !== null && typeof result2.leadName !== "string") {
150098
150573
  throw new Error("team startGoal returned an invalid result");
150099
150574
  }
150100
- topicRoot?.confirmConsumed();
150101
150575
  goal = { leadThreadId: result2.leadThreadId, leadName: result2.leadName };
150576
+ confirmConsumed = () => topicRoot?.confirmConsumed();
150102
150577
  } catch (error) {
150103
150578
  host.log.warn("chat could not start the team goal", { error: describeError(error) });
150104
- await deliver(binding, t("team.startFailed"));
150105
- return null;
150579
+ return {
150580
+ status: "settled",
150581
+ respond: async () => {
150582
+ await deliver(binding, t("team.startFailed"));
150583
+ }
150584
+ };
150106
150585
  }
150586
+ accepted?.onAccepted?.();
150587
+ confirmConsumed();
150107
150588
  await commitDurably("team-goal-started", {
150108
150589
  chatKey: chatKeyOf(binding),
150109
150590
  threadId: goal.leadThreadId
@@ -150117,7 +150598,7 @@ var makeTeamGoals = (core) => {
150117
150598
  bot: binding.displayName,
150118
150599
  threadId: goal.leadThreadId
150119
150600
  });
150120
- return goal.leadThreadId;
150601
+ return { status: "started", claimsPlaceholder: false };
150121
150602
  };
150122
150603
  return { hasRosterFor, startTeamGoal };
150123
150604
  };
@@ -151350,12 +151831,11 @@ var dispatchTurnWithReceipt = async (host, input, options) => {
151350
151831
  var DELAYS = [3e4, 12e4, 6e5];
151351
151832
  var MAX_DISPATCH_ATTEMPTS = DELAYS.length + 1;
151352
151833
  var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried, messageText2, firstAttachmentName) => {
151353
- if (deps.signal.aborted) return false;
151834
+ if (deps.signal.aborted) return { status: "deferred" };
151354
151835
  const previous = inbox?.entry?.attempts ?? 0;
151355
151836
  const attempt = Math.min(previous + 1, MAX_DISPATCH_ATTEMPTS);
151356
151837
  const nextAttemptAt = inbox?.entry?.nextAttemptAt;
151357
151838
  const incremented = previous < MAX_DISPATCH_ATTEMPTS;
151358
- if (incremented) await inbox?.saveRetry(attempt, nextAttemptAt);
151359
151839
  const reference2 = dispatchFailureReference(input.requestId);
151360
151840
  const notify = async (text3) => {
151361
151841
  try {
@@ -151368,6 +151848,18 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151368
151848
  });
151369
151849
  }
151370
151850
  };
151851
+ if (incremented) {
151852
+ try {
151853
+ await inbox?.saveRetry(attempt, nextAttemptAt);
151854
+ } catch (error) {
151855
+ core.host.log.warn("chat could not save a dispatch attempt; leaving it for replay", {
151856
+ threadId: input.threadId,
151857
+ requestId: input.requestId,
151858
+ error: describeError(error)
151859
+ });
151860
+ return { status: "settled", respond: () => notify(core.t("reply.inboundDeferred")) };
151861
+ }
151862
+ }
151371
151863
  try {
151372
151864
  if (incremented) {
151373
151865
  await dispatchTurnWithReceipt(core.host, input, {
@@ -151376,14 +151868,14 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151376
151868
  });
151377
151869
  } else {
151378
151870
  const status = await confirmTurnReceipt(core.host, input);
151379
- if (deps.signal.aborted) return false;
151871
+ if (deps.signal.aborted) return { status: "deferred" };
151380
151872
  if (status === "absent") throw new Error("attempt budget exhausted; receipt absent");
151381
151873
  }
151382
- return true;
151874
+ return { status: "accepted" };
151383
151875
  } catch (error) {
151384
151876
  if (deps.signal.aborted) {
151385
151877
  if (incremented) await inbox?.saveRetry(previous, nextAttemptAt);
151386
- return false;
151878
+ return { status: "deferred" };
151387
151879
  }
151388
151880
  const delay2 = DELAYS[attempt - 1];
151389
151881
  if (!(error instanceof TurnDispatchRejected) && delay2 !== void 0 && inbox?.entry !== void 0) {
@@ -151397,7 +151889,7 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151397
151889
  error: describeError(error)
151398
151890
  });
151399
151891
  if (attempt === 1) await notify(deps.failures.forDispatchRetry(input.requestId));
151400
- return false;
151892
+ return { status: "deferred" };
151401
151893
  }
151402
151894
  const redactor = botRedactor(
151403
151895
  core.adapterFor(binding.platform)?.columns ?? [],
@@ -151416,10 +151908,12 @@ var makeTurnRetry = (core, deps) => async (binding, input, inbox, restoreCarried
151416
151908
  const textExcerpt = messageText2.trim();
151417
151909
  const attachmentName = firstAttachmentName?.trim() ?? "";
151418
151910
  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;
151911
+ return {
151912
+ status: "settled",
151913
+ respond: () => notify(
151914
+ binding.reply === void 0 && attempt > 1 && identifyingNotice !== void 0 ? identifyingNotice : failureNotice
151915
+ )
151916
+ };
151423
151917
  }
151424
151918
  };
151425
151919
 
@@ -151473,6 +151967,7 @@ var makeTurns = (core, deps) => {
151473
151967
  const ensureThread = async (binding, message) => {
151474
151968
  const chatKey = chatKeyOf(binding);
151475
151969
  let threadId = chatOf(core.state, chatKey).threadId ?? void 0;
151970
+ let dropped = false;
151476
151971
  if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
151477
151972
  host.log.info("chat dropped a saved thread that is no longer usable", {
151478
151973
  bot: binding.displayName,
@@ -151480,7 +151975,7 @@ var makeTurns = (core, deps) => {
151480
151975
  });
151481
151976
  bindChat(chatKey, null);
151482
151977
  threadId = void 0;
151483
- await deliver(binding, t("reply.threadGone"));
151978
+ dropped = true;
151484
151979
  }
151485
151980
  if (threadId !== void 0) return threadId;
151486
151981
  const project = await projectOrNull2(host.projects, binding.projectId);
@@ -151489,7 +151984,13 @@ var makeTurns = (core, deps) => {
151489
151984
  bot: binding.displayName,
151490
151985
  projectId: binding.projectId
151491
151986
  });
151492
- return null;
151987
+ return {
151988
+ status: "settled",
151989
+ respond: async () => {
151990
+ if (dropped) await deliver(binding, t("reply.threadGone"));
151991
+ await deliver(binding, t("reply.projectMissing"));
151992
+ }
151993
+ };
151493
151994
  }
151494
151995
  const modelSelection = await resolveDefaultModel(
151495
151996
  binding,
@@ -151502,8 +152003,13 @@ var makeTurns = (core, deps) => {
151502
152003
  bot: binding.displayName,
151503
152004
  projectId: binding.projectId
151504
152005
  });
151505
- await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
151506
- return null;
152006
+ return {
152007
+ status: "settled",
152008
+ respond: async () => {
152009
+ if (dropped) await deliver(binding, t("reply.threadGone"));
152010
+ await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
152011
+ }
152012
+ };
151507
152013
  }
151508
152014
  const createdThreadId = identity2("thread", binding, message);
151509
152015
  const presetId = await resolveDefaultPreset(chatOf(core.state, chatKey).preset, binding, host);
@@ -151526,17 +152032,19 @@ var makeTurns = (core, deps) => {
151526
152032
  openedAt: (/* @__PURE__ */ new Date()).toISOString()
151527
152033
  });
151528
152034
  rememberThreadProject(createdThreadId, binding.projectId);
152035
+ if (dropped) await deliver(binding, t("reply.threadGone"));
151529
152036
  return createdThreadId;
151530
152037
  };
151531
152038
  const startTurn = async (binding, message, accepted, inbox) => {
151532
152039
  const chatKey = chatKeyOf(binding);
151533
152040
  const text3 = message.text;
151534
152041
  const replayed = inbox?.replayed === true || (inbox?.entry?.attempts ?? 0) > 0;
151535
- const threadId = inbox?.entry?.threadId ?? await ensureThread(binding, message);
151536
- if (threadId === null) {
152042
+ const prepared = inbox?.entry?.threadId ?? await ensureThread(binding, message);
152043
+ if (typeof prepared !== "string") {
151537
152044
  await inbox?.complete();
151538
- return null;
152045
+ return prepared;
151539
152046
  }
152047
+ const threadId = prepared;
151540
152048
  await inbox?.saveThread(threadId);
151541
152049
  const topicRoot = await message.topicRootForSession?.(threadId);
151542
152050
  const requestId = identity2("turn", binding, message);
@@ -151570,11 +152078,33 @@ var makeTurns = (core, deps) => {
151570
152078
  stored: [...saved.stored, ...own.attachments],
151571
152079
  notes: [...saved.notes, ...own.notes]
151572
152080
  };
151573
- await inbox?.saveAttachments(saved);
151574
152081
  } catch (error) {
151575
152082
  restoreCarried();
151576
152083
  throw error;
151577
152084
  }
152085
+ try {
152086
+ await inbox?.saveAttachments(saved);
152087
+ } catch (error) {
152088
+ restoreCarried();
152089
+ host.log.warn("chat could not save inbound attachments; leaving them for replay", {
152090
+ threadId,
152091
+ error: describeError(error)
152092
+ });
152093
+ return {
152094
+ status: "settled",
152095
+ respond: async () => {
152096
+ try {
152097
+ if (!await deliver(binding, t("reply.inboundDeferred")))
152098
+ host.log.warn("chat could not deliver an inbound deferral notice", { threadId });
152099
+ } catch (deliveryError) {
152100
+ host.log.warn("chat could not deliver an inbound deferral notice", {
152101
+ threadId,
152102
+ error: describeError(deliveryError)
152103
+ });
152104
+ }
152105
+ }
152106
+ };
152107
+ }
151578
152108
  }
151579
152109
  const attachments = saved.stored;
151580
152110
  const notes = saved.notes;
@@ -151634,7 +152164,8 @@ var makeTurns = (core, deps) => {
151634
152164
  message.text,
151635
152165
  message.resources[0]?.name
151636
152166
  );
151637
- if (!dispatched) return null;
152167
+ if (dispatched.status !== "accepted") return dispatched;
152168
+ accepted.onAccepted?.();
151638
152169
  topicRoot?.confirmConsumed();
151639
152170
  await deps.conclusions.owe(
151640
152171
  { chatKey, threadId, userMessageId, recordedAt: (/* @__PURE__ */ new Date()).toISOString() },
@@ -151658,11 +152189,12 @@ var makeTurns = (core, deps) => {
151658
152189
  binding.reply
151659
152190
  );
151660
152191
  });
151661
- return threadId;
152192
+ return { status: "started" };
151662
152193
  };
151663
152194
  const receiveAttachmentsOnly = async (binding, message, enteredWaiting) => {
151664
- const threadId = await ensureThread(binding, message);
151665
- if (threadId === null) return;
152195
+ const prepared = await ensureThread(binding, message);
152196
+ if (typeof prepared !== "string") return prepared;
152197
+ const threadId = prepared;
151666
152198
  const waiting = pendingFor(chatKeyOf(binding), threadId);
151667
152199
  const collected = await collectResources(
151668
152200
  binding,
@@ -151674,7 +152206,7 @@ var makeTurns = (core, deps) => {
151674
152206
  waiting.attachments.push(...collected.attachments);
151675
152207
  waiting.notes.push(...collected.notes.slice(0, MAX_ATTACHMENTS_PER_TURN));
151676
152208
  await enteredWaiting?.();
151677
- if (collected.attachments.length === 0) return;
152209
+ if (collected.attachments.length === 0) return { status: "settled" };
151678
152210
  await deliver(
151679
152211
  binding,
151680
152212
  t("reply.attachmentReceived", {
@@ -151682,6 +152214,7 @@ var makeTurns = (core, deps) => {
151682
152214
  count: waiting.attachments.length
151683
152215
  })
151684
152216
  );
152217
+ return { status: "settled" };
151685
152218
  };
151686
152219
  const hasWaitingAttachments = (chatKey) => (waitingFor(chatKey, chatOf(core.state, chatKey).threadId)?.attachments.length ?? 0) > 0;
151687
152220
  return {
@@ -155175,7 +155708,7 @@ var makeWecomAppAdapter = (deps) => {
155175
155708
  // src/index.ts
155176
155709
  var definition = {
155177
155710
  id: "im",
155178
- apiVersion: 27,
155711
+ apiVersion: 29,
155179
155712
  // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
155180
155713
  activate: async (ctx, testing = {}) => {
155181
155714
  const { dispatchSleep } = testing;
@@ -155276,7 +155809,7 @@ var definition = {
155276
155809
  t,
155277
155810
  adapters
155278
155811
  });
155279
- const acknowledgements = makeAcknowledgements(core);
155812
+ const acknowledgements = makeAcknowledgements(core, ctx.interval);
155280
155813
  const failures = makeFailureNotices(core);
155281
155814
  const replyAnchors = makeReplyAnchors(core.host);
155282
155815
  const conclusions = makeConclusionLedger(core);
@@ -155291,6 +155824,7 @@ var definition = {
155291
155824
  const teams = makeTeamGoals(core);
155292
155825
  const notifications = makeNotifications(core, {
155293
155826
  takeWorking: acknowledgements.takeWorking,
155827
+ stopTypingFor: acknowledgements.stopTypingFor,
155294
155828
  replyCards,
155295
155829
  failures,
155296
155830
  conclusions,
@@ -155330,6 +155864,7 @@ var definition = {
155330
155864
  });
155331
155865
  const chatSetup = makeChatSetup(core, {
155332
155866
  rows: projectRows,
155867
+ enqueueForChat: chatQueue.enqueueForChat,
155333
155868
  resumeMessage: (bot, message) => inbound.resumeMessage(bot, message),
155334
155869
  openPicker: (chatKey, rows, reply) => panelWiring.panel.openProjects(chatKey, rows, reply)
155335
155870
  });
@@ -155380,6 +155915,7 @@ var definition = {
155380
155915
  await inbound.stop();
155381
155916
  turns.stop();
155382
155917
  await chatQueue.closeAndDrain();
155918
+ await acknowledgements.stopDeferred();
155383
155919
  }
155384
155920
  });
155385
155921
  registerSettingsRowActions(