@ycodium-ai/plugin-im 0.2.2596 → 0.2.2634

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
@@ -126163,7 +126163,8 @@ import {
126163
126163
  readRequestResolved,
126164
126164
  readTurnProgress,
126165
126165
  REQUEST_RESOLVED_TRIGGER_KIND,
126166
- TURN_PROGRESS_TRIGGER_KIND
126166
+ TURN_PROGRESS_TRIGGER_KIND,
126167
+ USER_INPUT_PENDING_TRIGGER_KIND
126167
126168
  } from "@ycodium-ai/plugin-api";
126168
126169
 
126169
126170
  // src/chatCommand.ts
@@ -126205,6 +126206,8 @@ var isThreadCommand = (text2, chinese) => /^\/thread$/i.test(text2.trim()) || ch
126205
126206
  var ZH_ALIASES = {
126206
126207
  \u505C: "stop",
126207
126208
  \u505C\u6B62: "stop",
126209
+ // 不用单独的「继续」:那是人对 agent 说的话,不该被当成命令吞掉。
126210
+ \u7EE7\u7EED\u53D1\u9001: "continue",
126208
126211
  \u72B6\u6001: "status",
126209
126212
  \u65B0\u5BF9\u8BDD: "new",
126210
126213
  \u5E2E\u52A9: "help",
@@ -126223,6 +126226,7 @@ var resolveTeam = (query, teams) => {
126223
126226
  var parseChatCommand = (input) => {
126224
126227
  const trimmed = input.text.trim();
126225
126228
  if (/^\/stop$/i.test(trimmed)) return { kind: "stop" };
126229
+ if (/^\/continue$/i.test(trimmed)) return { kind: "continue" };
126226
126230
  if (/^\/status$/i.test(trimmed)) return { kind: "status" };
126227
126231
  if (/^\/new$/i.test(trimmed)) return { kind: "new" };
126228
126232
  if (/^\/help$/i.test(trimmed)) return { kind: "help" };
@@ -126254,6 +126258,7 @@ var parseChatCommand = (input) => {
126254
126258
  var helpText = (t, options = {}) => [
126255
126259
  t("help.title"),
126256
126260
  t("help.stop"),
126261
+ t("help.continue"),
126257
126262
  t("help.status"),
126258
126263
  t("help.new"),
126259
126264
  t("help.sessions"),
@@ -126278,9 +126283,20 @@ var IM_STATE_KEY = "chats";
126278
126283
  var IM_STATE_VERSION = 1;
126279
126284
  var OPENED_THREAD_LIMIT = 200;
126280
126285
  var APPROVAL_RECORD_LIMIT = 100;
126281
- var emptyState = () => ({ chats: {}, opened: {}, approvals: {}, turnCards: {} });
126286
+ var emptyState = () => ({
126287
+ chats: {},
126288
+ opened: {},
126289
+ approvals: {},
126290
+ turnCards: {},
126291
+ questions: {},
126292
+ queue: {}
126293
+ });
126294
+ var questionsOf = (state) => state.questions ?? {};
126295
+ var queueOf = (state) => state.queue ?? {};
126282
126296
  var openTurnCardsOf = (state) => state.turnCards ?? {};
126283
126297
  var TURN_CARD_RECORD_LIMIT = 50;
126298
+ var QUESTION_RECORD_LIMIT = 50;
126299
+ var QUEUE_RECORD_LIMIT = 50;
126284
126300
  var DEFAULT_CHAT_RECORD = {
126285
126301
  threadId: null,
126286
126302
  target: { kind: "thread" },
@@ -126301,6 +126317,31 @@ var findApprovalByToken = (state, token) => {
126301
126317
  var openApprovalOf = (state, chatKey) => Object.values(state.approvals).findLast(
126302
126318
  (approval) => approval.chatKey === chatKey && !approval.resolved
126303
126319
  );
126320
+ var findQuestionByToken = (state, token) => {
126321
+ for (const record of Object.values(questionsOf(state))) {
126322
+ for (const item of record.items) {
126323
+ const option = item.options.find((candidate) => candidate.token === token);
126324
+ if (option !== void 0) return { record, item, click: { kind: "option", option } };
126325
+ if (item.submitToken === token) return { record, item, click: { kind: "submit" } };
126326
+ }
126327
+ }
126328
+ return void 0;
126329
+ };
126330
+ var openQuestionOf = (state, chatKey, threadId) => Object.values(questionsOf(state)).findLast(
126331
+ (record) => record.chatKey === chatKey && record.threadId === threadId && !record.resolved
126332
+ );
126333
+ var isUnanswered = (item) => item.answer === void 0;
126334
+ var findQueueByToken = (state, token) => {
126335
+ for (const record of Object.values(queueOf(state))) {
126336
+ if (record.steerToken === token) return { record, op: "steer" };
126337
+ if (record.withdrawToken === token) return { record, op: "withdraw" };
126338
+ if (record.resumeToken === token) return { record, op: "resume" };
126339
+ }
126340
+ return void 0;
126341
+ };
126342
+ var openQueueOfThread = (state, threadId) => Object.values(queueOf(state)).filter(
126343
+ (record) => record.threadId === threadId && record.settled === void 0
126344
+ );
126304
126345
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
126305
126346
  var nonEmpty = (value) => typeof value === "string" && value !== "" ? value : null;
126306
126347
  var readTarget = (value) => {
@@ -126322,6 +126363,74 @@ var readButtons = (value) => {
126322
126363
  }
126323
126364
  return buttons.length === 0 ? null : buttons;
126324
126365
  };
126366
+ var readQuestionOptions = (value) => {
126367
+ if (!Array.isArray(value)) return null;
126368
+ const options = [];
126369
+ for (const entry of value) {
126370
+ if (!isRecord(entry)) return null;
126371
+ const token = nonEmpty(entry["token"]);
126372
+ const optionValue = nonEmpty(entry["value"]);
126373
+ if (token === null || optionValue === null || typeof entry["label"] !== "string") return null;
126374
+ options.push({
126375
+ token,
126376
+ value: optionValue,
126377
+ label: entry["label"],
126378
+ description: typeof entry["description"] === "string" ? entry["description"] : ""
126379
+ });
126380
+ }
126381
+ return options.length === 0 ? null : options;
126382
+ };
126383
+ var readQuestionItems = (value) => {
126384
+ if (!Array.isArray(value)) return null;
126385
+ const items = [];
126386
+ for (const entry of value) {
126387
+ if (!isRecord(entry)) return null;
126388
+ const id = nonEmpty(entry["id"]);
126389
+ const options = readQuestionOptions(entry["options"]);
126390
+ if (id === null || options === null || typeof entry["question"] !== "string") return null;
126391
+ const submitToken = nonEmpty(entry["submitToken"]);
126392
+ items.push({
126393
+ id,
126394
+ header: typeof entry["header"] === "string" ? entry["header"] : "",
126395
+ question: entry["question"],
126396
+ kind: entry["kind"] === "plan" ? "plan" : "question",
126397
+ multiSelect: entry["multiSelect"] === true,
126398
+ // 老事实没写这个字段时按「允许」处理:与宿主「没明说算 true」一致。
126399
+ allowFreeform: entry["allowFreeform"] !== false,
126400
+ options,
126401
+ ...submitToken === null ? {} : { submitToken },
126402
+ selected: []
126403
+ });
126404
+ }
126405
+ return items.length === 0 ? null : items;
126406
+ };
126407
+ var readAnswer = (value) => {
126408
+ if (typeof value === "string") return value;
126409
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
126410
+ return value;
126411
+ }
126412
+ return null;
126413
+ };
126414
+ var withQuestionItem = (state, requestId, questionId, patch) => {
126415
+ const record = requestId === null ? void 0 : questionsOf(state)[requestId];
126416
+ if (requestId === null || questionId === null || record === void 0) return state;
126417
+ if (!record.items.some((item) => item.id === questionId)) return state;
126418
+ return {
126419
+ ...state,
126420
+ questions: {
126421
+ ...questionsOf(state),
126422
+ [requestId]: {
126423
+ ...record,
126424
+ items: record.items.map((item) => item.id === questionId ? patch(item) : item)
126425
+ }
126426
+ }
126427
+ };
126428
+ };
126429
+ var withQueueRecord = (state, intentId, patch) => {
126430
+ const record = intentId === null ? void 0 : queueOf(state)[intentId];
126431
+ if (intentId === null || record === void 0) return state;
126432
+ return { ...state, queue: { ...queueOf(state), [intentId]: patch(record) } };
126433
+ };
126325
126434
  var keepNewest = (record, limit) => {
126326
126435
  const entries = Object.entries(record);
126327
126436
  return entries.length <= limit ? record : Object.fromEntries(entries.slice(entries.length - limit));
@@ -126405,6 +126514,124 @@ var foldImEvent = (state, name, data) => {
126405
126514
  approvals: { ...state.approvals, [requestId]: { ...existing, cardId } }
126406
126515
  };
126407
126516
  }
126517
+ case "question-offered": {
126518
+ const chatKey = nonEmpty(data["chatKey"]);
126519
+ const threadId = nonEmpty(data["threadId"]);
126520
+ const requestId = nonEmpty(data["requestId"]);
126521
+ const items = readQuestionItems(data["items"]);
126522
+ if (chatKey === null || threadId === null || requestId === null || items === null) {
126523
+ return state;
126524
+ }
126525
+ return {
126526
+ ...state,
126527
+ questions: keepNewest(
126528
+ {
126529
+ ...questionsOf(state),
126530
+ [requestId]: { chatKey, threadId, requestId, items, resolved: false }
126531
+ },
126532
+ QUESTION_RECORD_LIMIT
126533
+ )
126534
+ };
126535
+ }
126536
+ case "question-carded": {
126537
+ const cardId = nonEmpty(data["cardId"]);
126538
+ if (cardId === null) return state;
126539
+ return withQuestionItem(
126540
+ state,
126541
+ nonEmpty(data["requestId"]),
126542
+ nonEmpty(data["questionId"]),
126543
+ (item) => ({ ...item, cardId })
126544
+ );
126545
+ }
126546
+ case "question-selected": {
126547
+ const selected = readAnswer(data["selected"]);
126548
+ if (!Array.isArray(selected)) return state;
126549
+ return withQuestionItem(
126550
+ state,
126551
+ nonEmpty(data["requestId"]),
126552
+ nonEmpty(data["questionId"]),
126553
+ (item) => ({ ...item, selected })
126554
+ );
126555
+ }
126556
+ case "question-answered": {
126557
+ const answer = readAnswer(data["answer"]);
126558
+ if (answer === null) return state;
126559
+ const label = typeof data["label"] === "string" ? data["label"] : "";
126560
+ return withQuestionItem(
126561
+ state,
126562
+ nonEmpty(data["requestId"]),
126563
+ nonEmpty(data["questionId"]),
126564
+ (item) => ({ ...item, answer, answerLabel: label })
126565
+ );
126566
+ }
126567
+ case "question-resolved": {
126568
+ const requestId = nonEmpty(data["requestId"]);
126569
+ const existing = requestId === null ? void 0 : questionsOf(state)[requestId];
126570
+ if (requestId === null || existing === void 0) return state;
126571
+ const ending = data["ending"];
126572
+ return {
126573
+ ...state,
126574
+ questions: {
126575
+ ...questionsOf(state),
126576
+ [requestId]: {
126577
+ ...existing,
126578
+ resolved: true,
126579
+ ending: ending === "elsewhere" || ending === "cancelled" ? ending : "here"
126580
+ }
126581
+ }
126582
+ };
126583
+ }
126584
+ case "queue-offered": {
126585
+ const chatKey = nonEmpty(data["chatKey"]);
126586
+ const threadId = nonEmpty(data["threadId"]);
126587
+ const intentId = nonEmpty(data["intentId"]);
126588
+ const steerToken = nonEmpty(data["steerToken"]);
126589
+ const withdrawToken = nonEmpty(data["withdrawToken"]);
126590
+ const resumeToken = nonEmpty(data["resumeToken"]);
126591
+ if (chatKey === null || threadId === null || intentId === null || steerToken === null || withdrawToken === null || resumeToken === null) {
126592
+ return state;
126593
+ }
126594
+ return {
126595
+ ...state,
126596
+ queue: keepNewest(
126597
+ {
126598
+ ...queueOf(state),
126599
+ [intentId]: {
126600
+ chatKey,
126601
+ threadId,
126602
+ intentId,
126603
+ text: typeof data["text"] === "string" ? data["text"] : "",
126604
+ steerToken,
126605
+ withdrawToken,
126606
+ resumeToken,
126607
+ steering: false
126608
+ }
126609
+ },
126610
+ QUEUE_RECORD_LIMIT
126611
+ )
126612
+ };
126613
+ }
126614
+ case "queue-carded": {
126615
+ const cardId = nonEmpty(data["cardId"]);
126616
+ if (cardId === null) return state;
126617
+ return withQueueRecord(state, nonEmpty(data["intentId"]), (record) => ({
126618
+ ...record,
126619
+ cardId
126620
+ }));
126621
+ }
126622
+ case "queue-steering":
126623
+ return withQueueRecord(state, nonEmpty(data["intentId"]), (record) => ({
126624
+ ...record,
126625
+ steering: data["steering"] === true
126626
+ }));
126627
+ case "queue-settled": {
126628
+ const outcome = data["outcome"];
126629
+ if (outcome !== "steered" && outcome !== "sent" && outcome !== "removed") return state;
126630
+ return withQueueRecord(state, nonEmpty(data["intentId"]), (record) => ({
126631
+ ...record,
126632
+ settled: outcome
126633
+ }));
126634
+ }
126408
126635
  case "turn-card-opened": {
126409
126636
  const cardId = nonEmpty(data["cardId"]);
126410
126637
  const chatKey = nonEmpty(data["chatKey"]);
@@ -132009,6 +132236,47 @@ var buildPanelCard = (card) => {
132009
132236
  elements
132010
132237
  };
132011
132238
  };
132239
+ var CHOICE_TEMPLATE = {
132240
+ ask: "wathet",
132241
+ queue: "wathet",
132242
+ done: "grey"
132243
+ };
132244
+ var buildChoiceCard = (card) => {
132245
+ const elements = [];
132246
+ const lead = [
132247
+ card.heading === null || card.heading === "" ? "" : `**${stripAngleBrackets(card.heading).replaceAll("*", "")}**`,
132248
+ stripAngleBrackets(card.body)
132249
+ ].filter((part) => part !== "");
132250
+ if (lead.length > 0) elements.push({ tag: "markdown", content: lead.join("\n\n") });
132251
+ for (const row of card.rows) {
132252
+ if (row.length === 0) continue;
132253
+ elements.push({
132254
+ tag: "action",
132255
+ actions: row.map((button) => ({
132256
+ tag: "button",
132257
+ text: { tag: "plain_text", content: button.label },
132258
+ type: button.style,
132259
+ value: { tk: button.token }
132260
+ }))
132261
+ });
132262
+ }
132263
+ if (card.note !== null && card.note !== "") {
132264
+ if (elements.length > 0) elements.push({ tag: "hr" });
132265
+ elements.push({
132266
+ tag: "note",
132267
+ elements: [{ tag: "plain_text", content: card.note }]
132268
+ });
132269
+ }
132270
+ if (elements.length === 0) elements.push({ tag: "markdown", content: " " });
132271
+ return {
132272
+ config: { wide_screen_mode: true },
132273
+ header: {
132274
+ title: { tag: "plain_text", content: card.title },
132275
+ template: CHOICE_TEMPLATE[card.tone]
132276
+ },
132277
+ elements
132278
+ };
132279
+ };
132012
132280
 
132013
132281
  // src/larkInbound.ts
132014
132282
  var import_node_sdk = __toESM(require_lib2(), 1);
@@ -132244,7 +132512,7 @@ var makeLarkAdapter = (deps) => {
132244
132512
  appId,
132245
132513
  appSecret,
132246
132514
  onError: handlers.onError,
132247
- onCardAction: handlers.onApproval,
132515
+ onCardAction: handlers.onButton,
132248
132516
  onPanelClick: (click, operatorOpenId) => handlers.onPanelClick?.({
132249
132517
  chatId: click.chatId,
132250
132518
  cardId: click.cardId,
@@ -132346,6 +132614,22 @@ var makeLarkAdapter = (deps) => {
132346
132614
  });
132347
132615
  }
132348
132616
  },
132617
+ choiceCards: {
132618
+ open: async (binding, card) => sendCard({
132619
+ http: deps.http,
132620
+ token: await tenantToken(binding),
132621
+ chatId: binding.chatId,
132622
+ card: buildChoiceCard(card)
132623
+ }),
132624
+ update: async (binding, cardId, card) => {
132625
+ await patchCard({
132626
+ http: deps.http,
132627
+ token: await tenantToken(binding),
132628
+ messageId: cardId,
132629
+ card: buildChoiceCard(card)
132630
+ });
132631
+ }
132632
+ },
132349
132633
  acknowledge: async (binding, messageId) => addReaction({
132350
132634
  http: deps.http,
132351
132635
  token: await tenantToken(binding),
@@ -132470,6 +132754,44 @@ var en_default = {
132470
132754
  alreadyHandled: "Already handled",
132471
132755
  replyHint: "Reply with the number."
132472
132756
  },
132757
+ question: {
132758
+ title: "\u2753 Question",
132759
+ planTitle: "\u{1F4DD} Plan review",
132760
+ freeformHint: "Or reply with a message to answer in your own words.",
132761
+ replyOne: "Reply with the number.",
132762
+ replyMany: "Reply with the numbers, separated by commas.",
132763
+ submit: "\u2705 Submit",
132764
+ pickFirst: "Pick at least one option first.",
132765
+ answered: "Answer: {{answer}}",
132766
+ elsewhere: "Already answered elsewhere (the web app or another client).",
132767
+ cancelled: "This question was closed without an answer.",
132768
+ alreadyHandled: "Already handled",
132769
+ sendFailed: "Could not hand your answer to the agent. The question may have been answered elsewhere or already closed."
132770
+ },
132771
+ queue: {
132772
+ title: "\u23F3 Queued",
132773
+ waitRunning: "It goes out when the current reply finishes. To add it to the reply in progress now, tap Steer.",
132774
+ waitPaused: "Sending is paused (the last reply was stopped or failed). Tap Resume sending to carry on.",
132775
+ waitIdle: "It goes out shortly.",
132776
+ steer: "\u27A1\uFE0F Steer",
132777
+ withdraw: "\u21A9\uFE0F Withdraw",
132778
+ resume: "\u25B6\uFE0F Resume sending",
132779
+ steeringTitle: "\u23F3 Steering\u2026",
132780
+ steering: "\u{1F4AC} Adding it to the current reply\u2026",
132781
+ sendingTitle: "\u{1F4E4} Sending\u2026",
132782
+ sending: "Sending it now.",
132783
+ steeredTitle: "\u2705 Steered",
132784
+ steered: "Added to the current reply.",
132785
+ sentTitle: "\u{1F4E4} Sent",
132786
+ sent: "It went out as its own turn.",
132787
+ removedTitle: "\u{1F5D1}\uFE0F Withdrawn",
132788
+ removed: "Withdrawn. It will not be sent.",
132789
+ steerFailed: "Could not add it to the current reply (the reply may have just ended or changed). It stays queued and goes out when the reply finishes.",
132790
+ withdrawFailed: "Could not withdraw it: it has already started sending.",
132791
+ resumeFailed: "Could not resume sending. Try again in a moment.",
132792
+ alreadyHandled: "Already handled",
132793
+ plainNotice: "Queued: this message goes out when the current reply finishes."
132794
+ },
132473
132795
  command: {
132474
132796
  teamSelected: 'OK. From now on messages go to "{{title}}".',
132475
132797
  threadSelected: "OK. From now on messages start a normal session.",
@@ -132478,6 +132800,9 @@ var en_default = {
132478
132800
  newSession: "OK. The next message starts a new session.",
132479
132801
  stopNoSession: "Nothing is running in this group.",
132480
132802
  stopNoTurn: "No turn is running right now.",
132803
+ continued: "Resumed. The waiting messages are being sent.",
132804
+ continueNothing: "Nothing to resume: sending is not paused in this group.",
132805
+ continueFailed: "Could not resume sending. Try again in a moment.",
132481
132806
  stopped: "Stopped. The session is still there \u2014 just keep talking."
132482
132807
  },
132483
132808
  status: {
@@ -132499,12 +132824,13 @@ var en_default = {
132499
132824
  },
132500
132825
  help: {
132501
132826
  title: "Commands:",
132827
+ continue: "\xB7 /continue \u2014 resume sending after /stop or a failure paused it; the waiting messages go out",
132502
132828
  stop: "\xB7 /stop \u2014 stop the turn that is running; the session stays",
132503
132829
  status: "\xB7 /status \u2014 what is going on right now",
132504
132830
  new: "\xB7 /new \u2014 forget the current session; the next message starts a new one",
132505
132831
  team: "\xB7 /team name \u2014 send the next messages to a team; /team alone lists the teams",
132506
132832
  thread: "\xB7 /thread \u2014 go back to a normal session",
132507
- aliases: "Chinese shortcuts (\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
132833
+ 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\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
132508
132834
  sessions: "\xB7 /sessions \u2014 list the sessions this bot opened for this group's project",
132509
132835
  resume: "\xB7 /resume number \u2014 switch to one of them (a number from /sessions, or the start of its id)",
132510
132836
  model: "\xB7 /model \u2014 see the model this group uses; /model number or /model instance/model switches it",
@@ -132593,6 +132919,7 @@ var en_default = {
132593
132919
  },
132594
132920
  cmd: {
132595
132921
  new: "New session",
132922
+ continue: "Resume sending",
132596
132923
  stop: "Stop this turn",
132597
132924
  sessions: "Sessions",
132598
132925
  team: "Send to a team",
@@ -132720,6 +133047,44 @@ var zh_CN_default = {
132720
133047
  alreadyHandled: "\u5DF2\u5904\u7406",
132721
133048
  replyHint: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002"
132722
133049
  },
133050
+ question: {
133051
+ title: "\u2753 \u63D0\u95EE",
133052
+ planTitle: "\u{1F4DD} \u8BA1\u5212\u786E\u8BA4",
133053
+ freeformHint: "\u4E5F\u53EF\u4EE5\u76F4\u63A5\u56DE\u590D\u4E00\u6761\u6D88\u606F\uFF0C\u7528\u81EA\u5DF1\u7684\u8BDD\u4F5C\u7B54\u3002",
133054
+ replyOne: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002",
133055
+ replyMany: "\u56DE\u590D\u7F16\u53F7\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u9694\u5F00\u3002",
133056
+ submit: "\u2705 \u63D0\u4EA4",
133057
+ pickFirst: "\u5148\u9009\u4E00\u9879\u518D\u63D0\u4EA4\u3002",
133058
+ answered: "\u56DE\u7B54\uFF1A{{answer}}",
133059
+ elsewhere: "\u5DF2\u5728\u522B\u5904\u56DE\u7B54\uFF08\u7F51\u9875\u6216\u5176\u4ED6\u5BA2\u6237\u7AEF\uFF09\u3002",
133060
+ cancelled: "\u8FD9\u9053\u63D0\u95EE\u5DF2\u7ED3\u675F\uFF0C\u6CA1\u6709\u6536\u5230\u56DE\u7B54\u3002",
133061
+ alreadyHandled: "\u5DF2\u5904\u7406",
133062
+ sendFailed: "\u6CA1\u80FD\u628A\u4F60\u7684\u56DE\u7B54\u4EA4\u7ED9 agent\u3002\u8FD9\u9053\u63D0\u95EE\u53EF\u80FD\u5DF2\u5728\u522B\u5904\u56DE\u7B54\uFF0C\u6216\u5DF2\u7ECF\u7ED3\u675F\u3002"
133063
+ },
133064
+ queue: {
133065
+ title: "\u23F3 \u5DF2\u6392\u961F",
133066
+ waitRunning: "\u5F53\u524D\u8FD9\u4E00\u8F6E\u56DE\u590D\u7ED3\u675F\u540E\u81EA\u52A8\u53D1\u51FA\u3002\u60F3\u73B0\u5728\u5C31\u9001\u8FDB\u6B63\u5728\u8FDB\u884C\u7684\u8FD9\u4E00\u8F6E\uFF0C\u70B9\u300C\u63D2\u8BDD\u300D\u3002",
133067
+ waitPaused: "\u53D1\u9001\u5DF2\u6682\u505C\uFF08\u4E0A\u4E00\u8F6E\u88AB\u505C\u6B62\u6216\u51FA\u9519\u4E86\uFF09\u3002\u70B9\u300C\u6062\u590D\u53D1\u9001\u300D\u7EE7\u7EED\u3002",
133068
+ waitIdle: "\u9A6C\u4E0A\u53D1\u51FA\u3002",
133069
+ steer: "\u27A1\uFE0F \u63D2\u8BDD",
133070
+ withdraw: "\u21A9\uFE0F \u64A4\u56DE",
133071
+ resume: "\u25B6\uFE0F \u6062\u590D\u53D1\u9001",
133072
+ steeringTitle: "\u23F3 \u63D2\u8BDD\u4E2D\u2026",
133073
+ steering: "\u{1F4AC} \u6B63\u5728\u9001\u8FDB\u5F53\u524D\u8FD9\u4E00\u8F6E\u2026",
133074
+ sendingTitle: "\u{1F4E4} \u53D1\u9001\u4E2D\u2026",
133075
+ sending: "\u6B63\u5728\u53D1\u51FA\u3002",
133076
+ steeredTitle: "\u2705 \u5DF2\u63D2\u8BDD",
133077
+ steered: "\u5DF2\u9001\u8FDB\u5F53\u524D\u8FD9\u4E00\u8F6E\u3002",
133078
+ sentTitle: "\u{1F4E4} \u5DF2\u53D1\u9001",
133079
+ sent: "\u5DF2\u4F5C\u4E3A\u5355\u72EC\u7684\u4E00\u8F6E\u53D1\u51FA\u3002",
133080
+ removedTitle: "\u{1F5D1}\uFE0F \u5DF2\u64A4\u56DE",
133081
+ removed: "\u5DF2\u64A4\u56DE\uFF0C\u4E0D\u4F1A\u53D1\u51FA\u3002",
133082
+ steerFailed: "\u6CA1\u80FD\u9001\u8FDB\u5F53\u524D\u8FD9\u4E00\u8F6E\uFF08\u8FD9\u4E00\u8F6E\u53EF\u80FD\u521A\u7ED3\u675F\u6216\u5DF2\u7ECF\u6362\u4E86\uFF09\u3002\u6D88\u606F\u4ECD\u5728\u6392\u961F\uFF0C\u8FD9\u4E00\u8F6E\u7ED3\u675F\u540E\u53D1\u51FA\u3002",
133083
+ withdrawFailed: "\u6CA1\u80FD\u64A4\u56DE\uFF1A\u8FD9\u6761\u6D88\u606F\u5DF2\u7ECF\u5F00\u59CB\u53D1\u9001\u4E86\u3002",
133084
+ resumeFailed: "\u6CA1\u80FD\u6062\u590D\u53D1\u9001\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
133085
+ alreadyHandled: "\u5DF2\u5904\u7406",
133086
+ plainNotice: "\u5DF2\u6392\u961F\uFF1A\u5F53\u524D\u8FD9\u4E00\u8F6E\u56DE\u590D\u7ED3\u675F\u540E\u53D1\u51FA\u3002"
133087
+ },
132723
133088
  command: {
132724
133089
  teamSelected: "\u597D\uFF0C\u63A5\u4E0B\u6765\u6D3E\u7ED9\u300C{{title}}\u300D\u3002",
132725
133090
  threadSelected: "\u597D\uFF0C\u63A5\u4E0B\u6765\u5F00\u666E\u901A\u4F1A\u8BDD\u3002",
@@ -132728,6 +133093,9 @@ var zh_CN_default = {
132728
133093
  newSession: "\u597D\uFF0C\u4E0B\u4E00\u53E5\u8BDD\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002",
132729
133094
  stopNoSession: "\u8FD9\u4E2A\u7FA4\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u6D3B\u513F\u3002",
132730
133095
  stopNoTurn: "\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u4E00\u8F6E\u3002",
133096
+ continued: "\u5DF2\u6062\u590D\uFF0C\u6392\u7740\u7684\u6D88\u606F\u5F00\u59CB\u53D1\u9001\u3002",
133097
+ continueNothing: "\u6CA1\u6709\u53EF\u6062\u590D\u7684\uFF1A\u8FD9\u4E2A\u7FA4\u7684\u53D1\u9001\u6CA1\u6709\u88AB\u6682\u505C\u3002",
133098
+ continueFailed: "\u6CA1\u80FD\u6062\u590D\u53D1\u9001\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
132731
133099
  stopped: "\u505C\u4E86\u3002\u4F1A\u8BDD\u8FD8\u5728\uFF0C\u63A5\u7740\u8BF4\u5C31\u884C\u3002"
132732
133100
  },
132733
133101
  status: {
@@ -132749,12 +133117,13 @@ var zh_CN_default = {
132749
133117
  },
132750
133118
  help: {
132751
133119
  title: "\u80FD\u7528\u7684\u547D\u4EE4\uFF1A",
133120
+ 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",
132752
133121
  stop: "\xB7 /stop \u2014\u2014 \u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E\uFF0C\u4F1A\u8BDD\u7559\u7740",
132753
133122
  status: "\xB7 /status \u2014\u2014 \u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
132754
133123
  new: "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
132755
133124
  team: "\xB7 /team \u961F\u540D \u2014\u2014 \u628A\u63A5\u4E0B\u6765\u7684\u6D3B\u6D3E\u7ED9\u4E00\u652F\u961F\uFF1B/team \u4E0D\u5E26\u540D\u5B57\u770B\u6709\u54EA\u4E9B\u961F",
132756
133125
  thread: "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
132757
- aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
133126
+ 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\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
132758
133127
  sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD",
132759
133128
  resume: "\xB7 /resume \u7F16\u53F7 \u2014\u2014 \u5207\u5230\u5176\u4E2D\u4E00\u6761\uFF08/sessions \u91CC\u7684\u7F16\u53F7\uFF0C\u6216\u4F1A\u8BDD id \u7684\u5F00\u5934\uFF09",
132760
133129
  model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u7FA4\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
@@ -132843,6 +133212,7 @@ var zh_CN_default = {
132843
133212
  },
132844
133213
  cmd: {
132845
133214
  new: "\u65B0\u4F1A\u8BDD",
133215
+ continue: "\u6062\u590D\u53D1\u9001",
132846
133216
  stop: "\u505C\u6B62\u672C\u8F6E",
132847
133217
  sessions: "\u4F1A\u8BDD\u5217\u8868",
132848
133218
  team: "\u6D3E\u7ED9\u56E2\u961F",
@@ -133033,6 +133403,14 @@ var PANEL_COMMANDS = [
133033
133403
  labelKey: "panel.cmd.stop",
133034
133404
  action: { op: "run", arg: "stop" }
133035
133405
  },
133406
+ {
133407
+ id: "continue",
133408
+ category: "session",
133409
+ line: "/continue",
133410
+ kind: "continue",
133411
+ labelKey: "panel.cmd.continue",
133412
+ action: { op: "run", arg: "continue" }
133413
+ },
133036
133414
  {
133037
133415
  id: "sessions",
133038
133416
  category: "session",
@@ -133393,6 +133771,568 @@ var makePanelController = (deps) => {
133393
133771
  };
133394
133772
  };
133395
133773
 
133774
+ // src/flowHost.ts
133775
+ var NOT_MINE = { handled: false };
133776
+ var handledWith = (notice) => ({ handled: true, notice });
133777
+
133778
+ // src/questionCards.ts
133779
+ var QUESTION_TEXT_MAX_CHARS = 1500;
133780
+ var ANSWER_ECHO_MAX_CHARS = 200;
133781
+ var clip2 = (text2, limit) => {
133782
+ const chars = Array.from(text2.trim());
133783
+ return chars.length <= limit ? chars.join("") : `${chars.slice(0, limit).join("")}\u2026`;
133784
+ };
133785
+ var titleOf = (item, index, total, t) => {
133786
+ const base = item.kind === "plan" ? t("question.planTitle") : t("question.title");
133787
+ return total > 1 ? `${base} ${String(index + 1)}/${String(total)}` : base;
133788
+ };
133789
+ var bodyOf = (item) => clip2(
133790
+ item.options.filter((option) => option.description.trim() !== "").map((option) => `\xB7 ${option.label}: ${option.description.trim()}`).join("\n"),
133791
+ QUESTION_TEXT_MAX_CHARS
133792
+ );
133793
+ var headingOf = (item) => clip2(
133794
+ [item.header, item.question].filter((part) => part.trim() !== "").join("\n"),
133795
+ QUESTION_TEXT_MAX_CHARS
133796
+ );
133797
+ var questionCardOf = (item, index, total, t) => ({
133798
+ tone: "ask",
133799
+ title: titleOf(item, index, total, t),
133800
+ heading: headingOf(item),
133801
+ body: bodyOf(item),
133802
+ note: item.allowFreeform ? t("question.freeformHint") : null,
133803
+ rows: [
133804
+ ...item.options.map((option) => {
133805
+ const ticked = item.multiSelect && item.selected.includes(option.value);
133806
+ return [
133807
+ {
133808
+ label: ticked ? `\u2705 ${option.label}` : option.label,
133809
+ token: option.token,
133810
+ style: ticked ? "primary" : "default"
133811
+ }
133812
+ ];
133813
+ }),
133814
+ ...item.multiSelect && item.submitToken !== void 0 ? [[{ label: t("question.submit"), token: item.submitToken, style: "primary" }]] : []
133815
+ ]
133816
+ });
133817
+ var questionSettledCardOf = (item, index, total, ending, t) => ({
133818
+ tone: "done",
133819
+ title: titleOf(item, index, total, t),
133820
+ heading: headingOf(item),
133821
+ body: "",
133822
+ note: item.answer !== void 0 ? t("question.answered", { answer: item.answerLabel ?? "" }) : ending === "cancelled" ? t("question.cancelled") : t("question.elsewhere"),
133823
+ rows: []
133824
+ });
133825
+ var questionPlainText = (item, index, total, t) => [
133826
+ titleOf(item, index, total, t),
133827
+ headingOf(item),
133828
+ ...item.options.map((option, position) => {
133829
+ const description = option.description.trim();
133830
+ return `${String(position + 1)}. ${option.label}${description === "" ? "" : ` \u2014 ${description}`}`;
133831
+ }),
133832
+ "",
133833
+ item.multiSelect ? t("question.replyMany") : t("question.replyOne"),
133834
+ ...item.allowFreeform ? [t("question.freeformHint")] : []
133835
+ ].join("\n");
133836
+ var parseNumberedReply = (item, text2) => {
133837
+ const trimmed = text2.trim();
133838
+ const pattern = item.multiSelect ? /^\d+(?:[\s,,、]+\d+)*$/ : /^\d+$/;
133839
+ if (!pattern.test(trimmed)) return null;
133840
+ const numbers = [...new Set(trimmed.split(/[\s,,、]+/).map(Number))];
133841
+ const picked = numbers.map((number) => item.options[number - 1]);
133842
+ if (picked.some((option) => option === void 0)) return null;
133843
+ const options = item.options.filter((option) => picked.includes(option));
133844
+ if (item.multiSelect) {
133845
+ return {
133846
+ answer: options.map((option) => option.value),
133847
+ label: options.map((option) => option.label).join(", ")
133848
+ };
133849
+ }
133850
+ const only = options[0];
133851
+ return only === void 0 ? null : { answer: only.value, label: only.label };
133852
+ };
133853
+
133854
+ // src/questionFlow.ts
133855
+ var makeQuestionFlow = (host) => {
133856
+ const { t } = host;
133857
+ const recordOf = (requestId) => questionsOf(host.state())[requestId];
133858
+ const itemOf = (requestId, questionId) => recordOf(requestId)?.items.find((item) => item.id === questionId);
133859
+ const hasButtons = (chatKey) => host.target(chatKey)?.adapter.choiceCards !== void 0;
133860
+ const redraw = async (requestId, questionId) => {
133861
+ const record = recordOf(requestId);
133862
+ const item = itemOf(requestId, questionId);
133863
+ const target = record === void 0 ? void 0 : host.target(record.chatKey);
133864
+ if (record === void 0 || item?.cardId === void 0 || target === void 0) return;
133865
+ if (record.resolved || item.answer !== void 0) return;
133866
+ try {
133867
+ await target.adapter.choiceCards?.update(
133868
+ target.binding,
133869
+ item.cardId,
133870
+ questionCardOf(item, record.items.indexOf(item), record.items.length, t)
133871
+ );
133872
+ } catch (error) {
133873
+ host.warn("chat could not update the question card", error);
133874
+ }
133875
+ };
133876
+ const freeze = async (requestId, questionId) => {
133877
+ const record = recordOf(requestId);
133878
+ const item = itemOf(requestId, questionId);
133879
+ const target = record === void 0 ? void 0 : host.target(record.chatKey);
133880
+ if (record === void 0 || item?.cardId === void 0 || target === void 0) return;
133881
+ try {
133882
+ await target.adapter.choiceCards?.update(
133883
+ target.binding,
133884
+ item.cardId,
133885
+ questionSettledCardOf(
133886
+ item,
133887
+ record.items.indexOf(item),
133888
+ record.items.length,
133889
+ record.ending ?? "here",
133890
+ t
133891
+ )
133892
+ );
133893
+ } catch (error) {
133894
+ host.warn("chat could not update the question card", error);
133895
+ }
133896
+ };
133897
+ const sayNext = async (requestId) => {
133898
+ const record = recordOf(requestId);
133899
+ if (record === void 0 || record.resolved) return;
133900
+ const item = record.items.find(isUnanswered);
133901
+ if (item === void 0) return;
133902
+ await host.say(
133903
+ record.chatKey,
133904
+ questionPlainText(item, record.items.indexOf(item), record.items.length, t)
133905
+ );
133906
+ };
133907
+ const post = async (requestId) => {
133908
+ const first = recordOf(requestId);
133909
+ const target = first === void 0 ? void 0 : host.target(first.chatKey);
133910
+ if (first === void 0 || target === void 0) return;
133911
+ const cards = target.adapter.choiceCards;
133912
+ if (cards === void 0) {
133913
+ await sayNext(requestId);
133914
+ return;
133915
+ }
133916
+ for (const questionId of first.items.map((item) => item.id)) {
133917
+ const record = recordOf(requestId);
133918
+ const item = itemOf(requestId, questionId);
133919
+ if (record === void 0 || record.resolved || item === void 0) return;
133920
+ try {
133921
+ const cardId = await cards.open(
133922
+ target.binding,
133923
+ questionCardOf(item, record.items.indexOf(item), record.items.length, t)
133924
+ );
133925
+ if (cardId === null) continue;
133926
+ host.commit("question-carded", { requestId, questionId, cardId });
133927
+ const after = recordOf(requestId);
133928
+ if (after?.resolved === true || itemOf(requestId, questionId)?.answer !== void 0) {
133929
+ await freeze(requestId, questionId);
133930
+ }
133931
+ } catch (error) {
133932
+ host.warn("chat could not ask the question", error);
133933
+ }
133934
+ }
133935
+ };
133936
+ const offer = (chatKey, threadId, pending) => {
133937
+ const target = host.target(chatKey);
133938
+ if (target === void 0) return;
133939
+ const buttons = target.adapter.choiceCards !== void 0;
133940
+ if (!buttons && target.adapter.answerByText === void 0) {
133941
+ host.warn("chat cannot show questions on this platform");
133942
+ return;
133943
+ }
133944
+ const fresh = pending.filter((request) => recordOf(request.requestId) === void 0);
133945
+ for (const request of buttons ? fresh : fresh.slice(0, 1)) {
133946
+ host.commit("question-offered", {
133947
+ chatKey,
133948
+ threadId,
133949
+ requestId: request.requestId,
133950
+ items: request.questions.map((question) => ({
133951
+ id: question.id,
133952
+ header: question.header,
133953
+ question: question.question,
133954
+ kind: question.kind,
133955
+ multiSelect: question.multiSelect,
133956
+ allowFreeform: question.allowFreeform,
133957
+ options: question.options.map((option) => ({
133958
+ token: host.newToken(),
133959
+ label: option.label,
133960
+ value: option.value,
133961
+ description: option.description
133962
+ })),
133963
+ ...question.multiSelect ? { submitToken: host.newToken() } : {}
133964
+ }))
133965
+ });
133966
+ if (recordOf(request.requestId) === void 0) continue;
133967
+ host.outbound(chatKey, () => post(request.requestId));
133968
+ }
133969
+ };
133970
+ const answer = async (requestId, questionId, value, label) => {
133971
+ const before = recordOf(requestId);
133972
+ const item = itemOf(requestId, questionId);
133973
+ if (before === void 0 || before.resolved || item === void 0 || !isUnanswered(item)) {
133974
+ return false;
133975
+ }
133976
+ host.commit("question-answered", { requestId, questionId, answer: value, label });
133977
+ const after = recordOf(requestId);
133978
+ if (after === void 0) return false;
133979
+ const complete = after.items.every((entry) => !isUnanswered(entry));
133980
+ if (complete) host.commit("question-resolved", { requestId, ending: "here" });
133981
+ host.outbound(after.chatKey, () => freeze(requestId, questionId));
133982
+ if (!complete) {
133983
+ if (!hasButtons(after.chatKey)) host.outbound(after.chatKey, () => sayNext(requestId));
133984
+ return true;
133985
+ }
133986
+ try {
133987
+ await host.dispatch({
133988
+ type: "thread.user-input.respond",
133989
+ commandId: host.newId(),
133990
+ threadId: after.threadId,
133991
+ requestId,
133992
+ answers: Object.fromEntries(after.items.map((entry) => [entry.id, entry.answer])),
133993
+ createdAt: host.now()
133994
+ });
133995
+ } catch (error) {
133996
+ host.warn("chat could not hand the answer to the agent", error);
133997
+ await host.say(after.chatKey, t("question.sendFailed"));
133998
+ }
133999
+ return true;
134000
+ };
134001
+ const click = (token, operatorId) => {
134002
+ const found = findQuestionByToken(host.state(), token);
134003
+ if (found === void 0) return NOT_MINE;
134004
+ const { record, item } = found;
134005
+ if (record.resolved || !isUnanswered(item)) return handledWith(t("question.alreadyHandled"));
134006
+ if (!host.allowed(record.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
134007
+ const requestId = record.requestId;
134008
+ if (found.click.kind === "option") {
134009
+ const option = found.click.option;
134010
+ if (!item.multiSelect) {
134011
+ void answer(requestId, item.id, option.value, option.label);
134012
+ return handledWith(null);
134013
+ }
134014
+ const selected = item.selected.includes(option.value) ? item.selected.filter((value) => value !== option.value) : [...item.selected, option.value];
134015
+ host.commit("question-selected", { requestId, questionId: item.id, selected });
134016
+ host.outbound(record.chatKey, () => redraw(requestId, item.id));
134017
+ return handledWith(null);
134018
+ }
134019
+ const chosen = item.options.filter((option) => item.selected.includes(option.value));
134020
+ if (chosen.length === 0) return handledWith(t("question.pickFirst"));
134021
+ void answer(
134022
+ requestId,
134023
+ item.id,
134024
+ chosen.map((option) => option.value),
134025
+ chosen.map((option) => option.label).join(", ")
134026
+ );
134027
+ return handledWith(null);
134028
+ };
134029
+ const answerByText = async (chatKey, threadId, text2) => {
134030
+ const record = openQuestionOf(host.state(), chatKey, threadId);
134031
+ const trimmed = text2.trim();
134032
+ if (record === void 0 || trimmed === "") return false;
134033
+ const unanswered = record.items.filter(isUnanswered);
134034
+ const first = unanswered[0];
134035
+ if (first === void 0) return false;
134036
+ if (!hasButtons(chatKey)) {
134037
+ const numbered = parseNumberedReply(first, trimmed);
134038
+ if (numbered !== null) {
134039
+ return answer(record.requestId, first.id, numbered.answer, numbered.label);
134040
+ }
134041
+ }
134042
+ const freeform = unanswered.find((item) => item.allowFreeform);
134043
+ if (freeform === void 0) return false;
134044
+ return answer(record.requestId, freeform.id, trimmed, clip2(trimmed, ANSWER_ECHO_MAX_CHARS));
134045
+ };
134046
+ const resolved = (requestId, outcome) => {
134047
+ const record = recordOf(requestId);
134048
+ if (record === void 0 || record.resolved) return;
134049
+ const ending = outcome === "answered" ? "elsewhere" : "cancelled";
134050
+ host.commit("question-resolved", { requestId, ending });
134051
+ for (const item of record.items) {
134052
+ if (isUnanswered(item)) host.outbound(record.chatKey, () => freeze(requestId, item.id));
134053
+ }
134054
+ };
134055
+ return { offer, click, answerByText, resolved };
134056
+ };
134057
+
134058
+ // src/queueCards.ts
134059
+ var QUEUE_PREVIEW_MAX_CHARS = 200;
134060
+ var WAITING_STATUSES = /* @__PURE__ */ new Set(["queued", "blocked"]);
134061
+ var IN_FLIGHT_STATUSES = /* @__PURE__ */ new Set([
134062
+ "preparing",
134063
+ "dispatching",
134064
+ "reconciling"
134065
+ ]);
134066
+ var WITHDRAWN_STATUSES = /* @__PURE__ */ new Set(["cancelled", "abandoned"]);
134067
+ var queueViewOf = (record, facts, released) => {
134068
+ const status = facts.intent?.status;
134069
+ if (status === void 0) return null;
134070
+ if (WITHDRAWN_STATUSES.has(status)) return { kind: "settled", outcome: "removed" };
134071
+ if (IN_FLIGHT_STATUSES.has(status))
134072
+ return record.steering ? { kind: "steering" } : { kind: "sending" };
134073
+ if (!WAITING_STATUSES.has(status)) {
134074
+ return { kind: "settled", outcome: record.steering ? "steered" : "sent" };
134075
+ }
134076
+ if (record.steering && !released) return { kind: "steering" };
134077
+ return { kind: "queued", running: facts.activeTurnId !== null, paused: facts.queuePaused };
134078
+ };
134079
+ var SETTLED_TITLE = {
134080
+ steered: "queue.steeredTitle",
134081
+ sent: "queue.sentTitle",
134082
+ removed: "queue.removedTitle"
134083
+ };
134084
+ var SETTLED_NOTE = {
134085
+ steered: "queue.steered",
134086
+ sent: "queue.sent",
134087
+ removed: "queue.removed"
134088
+ };
134089
+ var queueCardOf = (record, view, note, t) => {
134090
+ const body = clip2(record.text, QUEUE_PREVIEW_MAX_CHARS);
134091
+ if (view.kind === "steering") {
134092
+ return {
134093
+ tone: "queue",
134094
+ title: t("queue.steeringTitle"),
134095
+ heading: null,
134096
+ body,
134097
+ note: t("queue.steering"),
134098
+ rows: []
134099
+ };
134100
+ }
134101
+ if (view.kind === "sending") {
134102
+ return {
134103
+ tone: "queue",
134104
+ title: t("queue.sendingTitle"),
134105
+ heading: null,
134106
+ body,
134107
+ note: t("queue.sending"),
134108
+ rows: []
134109
+ };
134110
+ }
134111
+ if (view.kind === "settled") {
134112
+ return {
134113
+ tone: "done",
134114
+ title: t(SETTLED_TITLE[view.outcome]),
134115
+ heading: null,
134116
+ body,
134117
+ note: t(SETTLED_NOTE[view.outcome]),
134118
+ rows: []
134119
+ };
134120
+ }
134121
+ const steerable = view.running && !view.paused;
134122
+ const state = view.paused ? t("queue.waitPaused") : view.running ? t("queue.waitRunning") : t("queue.waitIdle");
134123
+ return {
134124
+ tone: "queue",
134125
+ title: t("queue.title"),
134126
+ heading: null,
134127
+ body,
134128
+ note: note ?? state,
134129
+ rows: [
134130
+ [
134131
+ ...steerable ? [{ label: t("queue.steer"), token: record.steerToken, style: "primary" }] : [],
134132
+ { label: t("queue.withdraw"), token: record.withdrawToken, style: "default" }
134133
+ ],
134134
+ ...view.paused ? [[{ label: t("queue.resume"), token: record.resumeToken, style: "primary" }]] : []
134135
+ ]
134136
+ };
134137
+ };
134138
+ var queueViewKey = (view, note) => JSON.stringify([view, note]);
134139
+
134140
+ // src/queueFlow.ts
134141
+ var LIVE_STATUSES = /* @__PURE__ */ new Set([
134142
+ "queued",
134143
+ "preparing",
134144
+ "dispatching",
134145
+ "running",
134146
+ "reconciling",
134147
+ "blocked"
134148
+ ]);
134149
+ var makeQueueFlow = (host, deps) => {
134150
+ const { t } = host;
134151
+ const shown = /* @__PURE__ */ new Map();
134152
+ const notes = /* @__PURE__ */ new Map();
134153
+ const recordOf = (intentId) => queueOf(host.state())[intentId];
134154
+ const factsOf = (execution, intentId) => ({
134155
+ activeTurnId: execution.activeTurnId,
134156
+ queuePaused: execution.queuePaused,
134157
+ intent: execution.intents.find((intent) => intent.intentId === intentId)
134158
+ });
134159
+ const noteQueued = async (chatKey, threadId, messageId, text2) => {
134160
+ const target = host.target(chatKey);
134161
+ if (target === void 0) return;
134162
+ let execution;
134163
+ try {
134164
+ execution = await deps.readExecution(threadId);
134165
+ } catch (error) {
134166
+ host.warn("chat could not read the turn queue", error);
134167
+ return;
134168
+ }
134169
+ const intent = execution?.intents.find((entry) => entry.messageId === messageId);
134170
+ if (execution === null || intent === void 0 || intent.status !== "queued") return;
134171
+ const waiting = execution.activeTurnId !== null || execution.queuePaused || execution.intents.some(
134172
+ (other) => other.intentId !== intent.intentId && LIVE_STATUSES.has(other.status) && other.sequence < intent.sequence
134173
+ );
134174
+ if (!waiting) return;
134175
+ const cards = target.adapter.choiceCards;
134176
+ if (cards === void 0) {
134177
+ await host.say(chatKey, t("queue.plainNotice"));
134178
+ return;
134179
+ }
134180
+ const intentId = intent.intentId;
134181
+ host.commit("queue-offered", {
134182
+ chatKey,
134183
+ threadId,
134184
+ intentId,
134185
+ text: text2,
134186
+ steerToken: host.newToken(),
134187
+ withdrawToken: host.newToken(),
134188
+ resumeToken: host.newToken()
134189
+ });
134190
+ host.outbound(chatKey, async () => {
134191
+ const record = recordOf(intentId);
134192
+ if (record === void 0) return;
134193
+ try {
134194
+ const latest = await deps.readExecution(threadId) ?? execution;
134195
+ const view = queueViewOf(record, factsOf(latest, intentId), false) ?? {
134196
+ kind: "queued",
134197
+ running: latest.activeTurnId !== null,
134198
+ paused: latest.queuePaused
134199
+ };
134200
+ if (view.kind === "settled") {
134201
+ host.commit("queue-settled", { intentId, outcome: view.outcome });
134202
+ return;
134203
+ }
134204
+ const cardId = await cards.open(target.binding, queueCardOf(record, view, null, t));
134205
+ if (cardId === null) return;
134206
+ host.commit("queue-carded", { intentId, cardId });
134207
+ shown.set(intentId, queueViewKey(view, null));
134208
+ } catch (error) {
134209
+ host.warn("chat could not show the queued message", error);
134210
+ }
134211
+ });
134212
+ };
134213
+ const redraw = async (intentId, released) => {
134214
+ const record = recordOf(intentId);
134215
+ const target = record === void 0 ? void 0 : host.target(record.chatKey);
134216
+ if (record === void 0 || record.settled !== void 0 || target === void 0) return;
134217
+ if (record.cardId === void 0) return;
134218
+ let execution;
134219
+ try {
134220
+ execution = await deps.readExecution(record.threadId);
134221
+ } catch (error) {
134222
+ host.warn("chat could not read the turn queue", error);
134223
+ return;
134224
+ }
134225
+ if (execution === null) return;
134226
+ const view = queueViewOf(record, factsOf(execution, intentId), released);
134227
+ if (view === null) return;
134228
+ if (view.kind === "queued" && record.steering) {
134229
+ host.commit("queue-steering", { intentId, steering: false });
134230
+ notes.set(intentId, t("queue.steerFailed"));
134231
+ }
134232
+ if (view.kind === "settled") notes.delete(intentId);
134233
+ await paint(record, view);
134234
+ if (view.kind === "settled") {
134235
+ host.commit("queue-settled", { intentId, outcome: view.outcome });
134236
+ shown.delete(intentId);
134237
+ }
134238
+ };
134239
+ const paint = async (record, view) => {
134240
+ const target = host.target(record.chatKey);
134241
+ const cards = target?.adapter.choiceCards;
134242
+ if (target === void 0 || cards === void 0 || record.cardId === void 0) return;
134243
+ const note = notes.get(record.intentId) ?? null;
134244
+ const key = queueViewKey(view, note);
134245
+ if (shown.get(record.intentId) === key) return;
134246
+ try {
134247
+ await cards.update(target.binding, record.cardId, queueCardOf(record, view, note, t));
134248
+ shown.set(record.intentId, key);
134249
+ } catch (error) {
134250
+ host.warn("chat could not update the queued message card", error);
134251
+ }
134252
+ };
134253
+ const refresh = (threadId, released = null) => {
134254
+ for (const record of openQueueOfThread(host.state(), threadId)) {
134255
+ host.outbound(record.chatKey, () => redraw(record.intentId, record.intentId === released));
134256
+ }
134257
+ };
134258
+ const run = async (record, work, failure) => {
134259
+ notes.delete(record.intentId);
134260
+ let ok = false;
134261
+ try {
134262
+ ok = await work();
134263
+ } catch (error) {
134264
+ host.warn("chat could not change the queue", error);
134265
+ }
134266
+ if (!ok) notes.set(record.intentId, failure);
134267
+ host.outbound(record.chatKey, () => redraw(record.intentId, false));
134268
+ };
134269
+ const steer = async (record) => {
134270
+ const execution = await deps.readExecution(record.threadId);
134271
+ if (execution?.activeTurnId == null) return false;
134272
+ host.commit("queue-steering", { intentId: record.intentId, steering: true });
134273
+ try {
134274
+ await host.dispatch({
134275
+ type: "thread.turn-intent.run",
134276
+ commandId: host.newId(),
134277
+ threadId: record.threadId,
134278
+ intentId: record.intentId,
134279
+ mode: "steer",
134280
+ expectedTurnId: execution.activeTurnId,
134281
+ createdAt: host.now()
134282
+ });
134283
+ return true;
134284
+ } catch (error) {
134285
+ host.commit("queue-steering", { intentId: record.intentId, steering: false });
134286
+ throw error;
134287
+ }
134288
+ };
134289
+ const click = (token, operatorId) => {
134290
+ const found = findQueueByToken(host.state(), token);
134291
+ if (found === void 0) return NOT_MINE;
134292
+ const { record, op } = found;
134293
+ if (record.settled !== void 0) return handledWith(t("queue.alreadyHandled"));
134294
+ if (!host.allowed(record.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
134295
+ if (op === "steer") {
134296
+ if (record.steering) return handledWith(t("queue.alreadyHandled"));
134297
+ void run(record, () => steer(record), t("queue.steerFailed"));
134298
+ } else if (op === "withdraw") {
134299
+ void run(
134300
+ record,
134301
+ async () => {
134302
+ await host.dispatch({
134303
+ type: "thread.turn-intent.cancel",
134304
+ commandId: host.newId(),
134305
+ threadId: record.threadId,
134306
+ intentId: record.intentId,
134307
+ // 只撤还没开始发送的:已经在路上的由宿主拒绝,而不是被当成「已放弃」。
134308
+ expectedStatus: "queued",
134309
+ expectedDeliveryCertainty: "not-dispatched",
134310
+ createdAt: host.now()
134311
+ });
134312
+ return true;
134313
+ },
134314
+ t("queue.withdrawFailed")
134315
+ );
134316
+ } else {
134317
+ void run(
134318
+ record,
134319
+ async () => {
134320
+ await host.dispatch({
134321
+ type: "thread.turn-queue.resume",
134322
+ commandId: host.newId(),
134323
+ threadId: record.threadId,
134324
+ createdAt: host.now()
134325
+ });
134326
+ return true;
134327
+ },
134328
+ t("queue.resumeFailed")
134329
+ );
134330
+ }
134331
+ return handledWith(null);
134332
+ };
134333
+ return { noteQueued, refresh, click };
134334
+ };
134335
+
133396
134336
  // src/telegramApi.ts
133397
134337
  var API_BASE = "https://api.telegram.org";
133398
134338
  var POLL_TIMEOUT_SECONDS = 45;
@@ -133579,6 +134519,14 @@ var makeTelegramApi = (input) => {
133579
134519
  };
133580
134520
  };
133581
134521
 
134522
+ // src/telegramChoice.ts
134523
+ var renderChoiceCard = (card) => ({
134524
+ text: [card.title, card.heading ?? "", card.body, card.note ?? ""].filter((part) => part !== "").join("\n\n"),
134525
+ rows: card.rows.filter((row) => row.length > 0).map(
134526
+ (row) => row.map((button) => ({ text: button.label, data: button.token }))
134527
+ )
134528
+ });
134529
+
133582
134530
  // src/telegramPanel.ts
133583
134531
  var PANEL_DATA_PREFIX = "pn:";
133584
134532
  var renderPanel = (card, version) => {
@@ -133609,7 +134557,7 @@ var parsePanelData = (data) => {
133609
134557
 
133610
134558
  // src/plainTurnText.ts
133611
134559
  var RUNNING_LABEL_MAX_CHARS = 200;
133612
- var clip2 = (text2, max) => {
134560
+ var clip3 = (text2, max) => {
133613
134561
  const chars = Array.from(text2);
133614
134562
  return chars.length <= max ? text2 : `${chars.slice(0, max - 1).join("")}\u2026`;
133615
134563
  };
@@ -133618,7 +134566,7 @@ var turnTextParts = (view, t) => {
133618
134566
  if (view.omitted > 0) names.unshift(t("card.step.omitted", { count: view.omitted }));
133619
134567
  const headLines = [view.title === "" ? t("card.untitled") : view.title];
133620
134568
  if (names.length > 0) headLines.push(names.join(" \u2192 "));
133621
- if (view.running !== null) headLines.push(`\u{1F527} ${clip2(view.running, RUNNING_LABEL_MAX_CHARS)}`);
134569
+ if (view.running !== null) headLines.push(`\u{1F527} ${clip3(view.running, RUNNING_LABEL_MAX_CHARS)}`);
133622
134570
  const body = view.body.trim();
133623
134571
  const footLines = [];
133624
134572
  if (view.status === "working") {
@@ -133786,7 +134734,7 @@ var makeTelegramAdapter = (deps) => {
133786
134734
  const callback = update.callback_query;
133787
134735
  if (callback?.data !== void 0) {
133788
134736
  const operatorId = callback.from === void 0 ? null : String(callback.from.id);
133789
- const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(callback, operatorId, handlers) : handlers.onApproval(callback.data, operatorId);
134737
+ const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(callback, operatorId, handlers) : handlers.onButton(callback.data, operatorId);
133790
134738
  await api.answerCallback(callback.id, notice ?? void 0).catch(() => void 0);
133791
134739
  continue;
133792
134740
  }
@@ -133906,6 +134854,20 @@ ${line}`
133906
134854
  panelKeyboards.set(key, { version, actions: rendered.actions });
133907
134855
  }
133908
134856
  },
134857
+ choiceCards: {
134858
+ open: async (binding, card) => {
134859
+ const api = await apiFor(binding);
134860
+ const rendered = renderChoiceCard(card);
134861
+ return withFloodWait(() => api.sendOne(binding.chatId, rendered.text, rendered.rows));
134862
+ },
134863
+ update: async (binding, cardId, card) => {
134864
+ const api = await apiFor(binding);
134865
+ const rendered = renderChoiceCard(card);
134866
+ await withFloodWait(
134867
+ () => api.editMessage(binding.chatId, cardId, rendered.text, rendered.rows)
134868
+ );
134869
+ }
134870
+ },
133909
134871
  fetchResource: async (binding, input) => {
133910
134872
  const api = await apiFor(binding);
133911
134873
  return api.fetchFile(input.resource.key);
@@ -135097,13 +136059,116 @@ ${final.text}`;
135097
136059
  });
135098
136060
  ctx.events.on(REQUEST_RESOLVED_TRIGGER_KIND, async (event) => {
135099
136061
  const resolved = readRequestResolved(event);
135100
- if (resolved === null || resolved.requestKind !== "approval") return;
136062
+ if (resolved === null) return;
135101
136063
  await ready;
136064
+ if (resolved.requestKind === "user-input") {
136065
+ questionFlow.resolved(
136066
+ resolved.requestId,
136067
+ resolved.outcome === "cancelled" ? "cancelled" : "answered"
136068
+ );
136069
+ return;
136070
+ }
135102
136071
  const record = state.approvals[resolved.requestId];
135103
136072
  if (record === void 0 || record.resolved) return;
135104
136073
  commit("approval-resolved", { requestId: resolved.requestId });
135105
136074
  outbound(record.chatKey, () => settleApprovalCard(record, { kind: "elsewhere" }));
135106
136075
  });
136076
+ const bindingOfThread = (threadId) => readBindings().find((candidate) => chatOf(state, chatKeyOf(candidate)).threadId === threadId);
136077
+ const flowHost = {
136078
+ t,
136079
+ state: () => state,
136080
+ commit,
136081
+ // 改卡必须排在回调应答之后:按钮点击的回调要先同步应答,飞书在应答不带改卡时会把卡
136082
+ // 恢复成点击前的样子;所以这两条流的发卡与改卡都推迟一个宏任务再入群队列。
136083
+ outbound: (chatKey, job) => {
136084
+ setTimeout(() => outbound(chatKey, job), 0);
136085
+ },
136086
+ target: (chatKey) => {
136087
+ const binding = bindingOfChat(chatKey);
136088
+ const adapter = binding === void 0 ? void 0 : adapterFor(binding.platform);
136089
+ return binding === void 0 || adapter === void 0 ? void 0 : { binding, adapter };
136090
+ },
136091
+ allowed: (chatKey, operatorId) => {
136092
+ const binding = bindingOfChat(chatKey);
136093
+ if (binding !== void 0 && isAllowedSender(binding, operatorId)) return true;
136094
+ ctx.log.warn("chat ignored a button click from someone not on the allow list", {
136095
+ group: binding?.displayName ?? chatKey,
136096
+ operator: operatorId
136097
+ });
136098
+ return false;
136099
+ },
136100
+ dispatch: (command) => ctx.commands.dispatch(command),
136101
+ say: async (chatKey, text2) => {
136102
+ const binding = bindingOfChat(chatKey);
136103
+ if (binding !== void 0) await deliver(binding, text2);
136104
+ },
136105
+ newToken: () => crypto.randomUUID().slice(0, 8),
136106
+ newId: () => crypto.randomUUID(),
136107
+ now: () => (/* @__PURE__ */ new Date()).toISOString(),
136108
+ warn: (message, error) => {
136109
+ ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError(error) });
136110
+ }
136111
+ };
136112
+ const questionFlow = makeQuestionFlow(flowHost);
136113
+ const queueFlow = makeQueueFlow(flowHost, {
136114
+ readExecution: async (threadId) => {
136115
+ const execution = await ctx.projections.getThreadExecution(threadId);
136116
+ if (execution === null) return null;
136117
+ return {
136118
+ activeTurnId: execution.session?.activeTurnId ?? null,
136119
+ queuePaused: execution.queuePaused,
136120
+ intents: execution.turnIntents.map((intent) => ({
136121
+ intentId: intent.intentId,
136122
+ messageId: intent.messageId,
136123
+ status: intent.status,
136124
+ sequence: intent.sequence
136125
+ }))
136126
+ };
136127
+ }
136128
+ });
136129
+ ctx.events.on(USER_INPUT_PENDING_TRIGGER_KIND, async (event) => {
136130
+ const threadId = event.threadId;
136131
+ if (threadId === null) return;
136132
+ await ready;
136133
+ const binding = bindingOfThread(threadId);
136134
+ if (binding === void 0) return;
136135
+ if (ctx.projections.listPendingUserInputs === void 0) {
136136
+ ctx.log.warn("chat cannot show questions: the host cannot list them");
136137
+ return;
136138
+ }
136139
+ try {
136140
+ questionFlow.offer(
136141
+ chatKeyOf(binding),
136142
+ threadId,
136143
+ await ctx.projections.listPendingUserInputs(threadId)
136144
+ );
136145
+ } catch (error) {
136146
+ ctx.log.warn("chat could not read the pending questions", { error: describeError(error) });
136147
+ }
136148
+ });
136149
+ ctx.reactor({
136150
+ name: "im.queue-cards",
136151
+ events: [
136152
+ "thread.session-set",
136153
+ "thread.turn-intent-transitioned",
136154
+ "thread.turn-queue-updated",
136155
+ "thread.turn-queue-paused"
136156
+ ],
136157
+ handle: async (event) => {
136158
+ const payload = event.payload;
136159
+ if (typeof payload.threadId !== "string") return;
136160
+ await ready;
136161
+ const released = event.type === "thread.turn-intent-transitioned" && payload.status === "queued" && (payload.fromStatus !== "queued" || typeof payload.blockedReason === "string") && typeof payload.intentId === "string" ? payload.intentId : null;
136162
+ queueFlow.refresh(payload.threadId, released);
136163
+ }
136164
+ });
136165
+ const handleButtonClick = (token, operatorId) => {
136166
+ const question = questionFlow.click(token, operatorId);
136167
+ if (question.handled) return question.notice;
136168
+ const queued = queueFlow.click(token, operatorId);
136169
+ if (queued.handled) return queued.notice;
136170
+ return handleApprovalClick(token, operatorId);
136171
+ };
135107
136172
  const isAllowedSender = (binding, sender) => binding.allowedSenders.size === 0 || sender !== null && binding.allowedSenders.has(sender);
135108
136173
  const handleApprovalClick = (token, operatorId) => {
135109
136174
  const found = findApprovalByToken(state, token);
@@ -135409,6 +136474,37 @@ ${final.text}`;
135409
136474
  );
135410
136475
  return true;
135411
136476
  }
136477
+ case "continue": {
136478
+ const threadId = chatOf(state, chatKey).threadId ?? void 0;
136479
+ let paused = false;
136480
+ if (threadId !== void 0) {
136481
+ try {
136482
+ paused = (await ctx.projections.getThreadExecution(threadId))?.queuePaused === true;
136483
+ } catch (error) {
136484
+ ctx.log.warn("chat could not read the turn queue", { error: describeError(error) });
136485
+ await deliver(binding, t("command.continueFailed"));
136486
+ return true;
136487
+ }
136488
+ }
136489
+ if (threadId === void 0 || !paused) {
136490
+ await deliver(binding, t("command.continueNothing"));
136491
+ return true;
136492
+ }
136493
+ try {
136494
+ await ctx.commands.dispatch({
136495
+ type: "thread.turn-queue.resume",
136496
+ commandId: crypto.randomUUID(),
136497
+ threadId,
136498
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
136499
+ });
136500
+ } catch (error) {
136501
+ ctx.log.warn("chat could not resume the turn queue", { error: describeError(error) });
136502
+ await deliver(binding, t("command.continueFailed"));
136503
+ return true;
136504
+ }
136505
+ await deliver(binding, t("command.continued"));
136506
+ return true;
136507
+ }
135412
136508
  case "stop": {
135413
136509
  const threadId = chatOf(state, chatKey).threadId ?? void 0;
135414
136510
  if (threadId === void 0) {
@@ -135520,12 +136616,13 @@ ${final.text}`;
135520
136616
  projectByThread.set(createdThreadId, binding.projectId);
135521
136617
  }
135522
136618
  const attachments = await collectAttachment(binding, threadId, message);
136619
+ const userMessageId = crypto.randomUUID();
135523
136620
  await ctx.commands.dispatch({
135524
136621
  type: "thread.turn.start",
135525
136622
  commandId: crypto.randomUUID(),
135526
136623
  threadId,
135527
136624
  message: {
135528
- messageId: crypto.randomUUID(),
136625
+ messageId: userMessageId,
135529
136626
  role: "user",
135530
136627
  text: text2,
135531
136628
  attachments
@@ -135540,6 +136637,12 @@ ${final.text}`;
135540
136637
  chatId: binding.chatId,
135541
136638
  threadId
135542
136639
  });
136640
+ await queueFlow.noteQueued(
136641
+ chatKey,
136642
+ threadId,
136643
+ userMessageId,
136644
+ text2.trim() || attachments[0]?.name || message.resource?.name || ""
136645
+ );
135543
136646
  return threadId;
135544
136647
  };
135545
136648
  const receiving = readBindings().filter((binding) => binding.projectId !== "");
@@ -135622,7 +136725,7 @@ ${final.text}`;
135622
136725
  };
135623
136726
  const inboundHandlersFor = (platform) => ({
135624
136727
  onError: (message) => ctx.log.warn("chat inbound connection failed", { platform, message }),
135625
- onApproval: handleApprovalClick,
136728
+ onButton: handleButtonClick,
135626
136729
  onPanelClick: (click) => handlePanelClick(platform, click),
135627
136730
  onMessage: (message) => {
135628
136731
  const target = bindingForChat(platform, message.chatId);
@@ -135650,6 +136753,10 @@ ${final.text}`;
135650
136753
  enqueueForChat(chatKeyOf(target), async () => {
135651
136754
  const teams = await fetchTeams(target);
135652
136755
  if (await handleCommand(target, message.text, teams ?? [])) return;
136756
+ const boundThread = chatOf(state, chatKeyOf(target)).threadId;
136757
+ if (boundThread !== null && await questionFlow.answerByText(chatKeyOf(target), boundThread, message.text)) {
136758
+ return;
136759
+ }
135653
136760
  const project = await ctx.projections.getProject(target.projectId);
135654
136761
  if (project === null || project.archivedAt != null) {
135655
136762
  if (project === null) {