@ycodium-ai/plugin-im 0.2.2958 → 0.2.2966

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
@@ -22669,24 +22669,24 @@ var require_sender = __commonJS({
22669
22669
  * @public
22670
22670
  */
22671
22671
  ping(data, mask, cb) {
22672
- let byteLength;
22672
+ let byteLength2;
22673
22673
  let readOnly;
22674
22674
  if (typeof data === "string") {
22675
- byteLength = Buffer.byteLength(data);
22675
+ byteLength2 = Buffer.byteLength(data);
22676
22676
  readOnly = false;
22677
22677
  } else if (isBlob(data)) {
22678
- byteLength = data.size;
22678
+ byteLength2 = data.size;
22679
22679
  readOnly = false;
22680
22680
  } else {
22681
22681
  data = toBuffer(data);
22682
- byteLength = data.length;
22682
+ byteLength2 = data.length;
22683
22683
  readOnly = toBuffer.readOnly;
22684
22684
  }
22685
- if (byteLength > 125) {
22685
+ if (byteLength2 > 125) {
22686
22686
  throw new RangeError("The data size must not be greater than 125 bytes");
22687
22687
  }
22688
22688
  const options = {
22689
- [kByteLength]: byteLength,
22689
+ [kByteLength]: byteLength2,
22690
22690
  fin: true,
22691
22691
  generateMask: this._generateMask,
22692
22692
  mask,
@@ -22716,24 +22716,24 @@ var require_sender = __commonJS({
22716
22716
  * @public
22717
22717
  */
22718
22718
  pong(data, mask, cb) {
22719
- let byteLength;
22719
+ let byteLength2;
22720
22720
  let readOnly;
22721
22721
  if (typeof data === "string") {
22722
- byteLength = Buffer.byteLength(data);
22722
+ byteLength2 = Buffer.byteLength(data);
22723
22723
  readOnly = false;
22724
22724
  } else if (isBlob(data)) {
22725
- byteLength = data.size;
22725
+ byteLength2 = data.size;
22726
22726
  readOnly = false;
22727
22727
  } else {
22728
22728
  data = toBuffer(data);
22729
- byteLength = data.length;
22729
+ byteLength2 = data.length;
22730
22730
  readOnly = toBuffer.readOnly;
22731
22731
  }
22732
- if (byteLength > 125) {
22732
+ if (byteLength2 > 125) {
22733
22733
  throw new RangeError("The data size must not be greater than 125 bytes");
22734
22734
  }
22735
22735
  const options = {
22736
- [kByteLength]: byteLength,
22736
+ [kByteLength]: byteLength2,
22737
22737
  fin: true,
22738
22738
  generateMask: this._generateMask,
22739
22739
  mask,
@@ -22774,23 +22774,23 @@ var require_sender = __commonJS({
22774
22774
  const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
22775
22775
  let opcode = options.binary ? 2 : 1;
22776
22776
  let rsv1 = options.compress;
22777
- let byteLength;
22777
+ let byteLength2;
22778
22778
  let readOnly;
22779
22779
  if (typeof data === "string") {
22780
- byteLength = Buffer.byteLength(data);
22780
+ byteLength2 = Buffer.byteLength(data);
22781
22781
  readOnly = false;
22782
22782
  } else if (isBlob(data)) {
22783
- byteLength = data.size;
22783
+ byteLength2 = data.size;
22784
22784
  readOnly = false;
22785
22785
  } else {
22786
22786
  data = toBuffer(data);
22787
- byteLength = data.length;
22787
+ byteLength2 = data.length;
22788
22788
  readOnly = toBuffer.readOnly;
22789
22789
  }
22790
22790
  if (this._firstFragment) {
22791
22791
  this._firstFragment = false;
22792
22792
  if (rsv1 && perMessageDeflate && perMessageDeflate.params[perMessageDeflate._isServer ? "server_no_context_takeover" : "client_no_context_takeover"]) {
22793
- rsv1 = byteLength >= perMessageDeflate._threshold;
22793
+ rsv1 = byteLength2 >= perMessageDeflate._threshold;
22794
22794
  }
22795
22795
  this._compress = rsv1;
22796
22796
  } else {
@@ -22799,7 +22799,7 @@ var require_sender = __commonJS({
22799
22799
  }
22800
22800
  if (options.fin) this._firstFragment = true;
22801
22801
  const opts = {
22802
- [kByteLength]: byteLength,
22802
+ [kByteLength]: byteLength2,
22803
22803
  fin: options.fin,
22804
22804
  generateMask: this._generateMask,
22805
22805
  mask: options.mask,
@@ -23360,7 +23360,7 @@ var require_websocket = __commonJS({
23360
23360
  var http = __require("http");
23361
23361
  var net = __require("net");
23362
23362
  var tls = __require("tls");
23363
- var { randomBytes, createHash: createHash2 } = __require("crypto");
23363
+ var { randomBytes, createHash: createHash3 } = __require("crypto");
23364
23364
  var { Duplex, Readable } = __require("stream");
23365
23365
  var { URL: URL2 } = __require("url");
23366
23366
  var PerMessageDeflate = require_permessage_deflate();
@@ -24028,7 +24028,7 @@ var require_websocket = __commonJS({
24028
24028
  abortHandshake(websocket, socket, "Invalid Upgrade header");
24029
24029
  return;
24030
24030
  }
24031
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
24031
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
24032
24032
  if (res.headers["sec-websocket-accept"] !== digest) {
24033
24033
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
24034
24034
  return;
@@ -24397,7 +24397,7 @@ var require_websocket_server = __commonJS({
24397
24397
  var EventEmitter = __require("events");
24398
24398
  var http = __require("http");
24399
24399
  var { Duplex } = __require("stream");
24400
- var { createHash: createHash2 } = __require("crypto");
24400
+ var { createHash: createHash3 } = __require("crypto");
24401
24401
  var extension = require_extension();
24402
24402
  var PerMessageDeflate = require_permessage_deflate();
24403
24403
  var subprotocol = require_subprotocol();
@@ -24704,7 +24704,7 @@ var require_websocket_server = __commonJS({
24704
24704
  );
24705
24705
  }
24706
24706
  if (this._state > RUNNING) return abortHandshake(socket, 503);
24707
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
24707
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
24708
24708
  const headers = [
24709
24709
  "HTTP/1.1 101 Switching Protocols",
24710
24710
  "Upgrade: websocket",
@@ -126159,11 +126159,67 @@ ${lines.join("\n")}
126159
126159
  // src/index.ts
126160
126160
  import { definePlugin } from "@ycodium-ai/plugin-api";
126161
126161
 
126162
+ // src/errors.ts
126163
+ var describeError = (error) => error instanceof Error ? error.message : String(error);
126164
+
126165
+ // src/acknowledgements.ts
126166
+ var makeAcknowledgements = (core) => {
126167
+ const { host, adapterFor } = core;
126168
+ const acknowledgements = /* @__PURE__ */ new Map();
126169
+ const markWorking = async (binding, threadId, messageId) => {
126170
+ const adapter = adapterFor(binding.platform);
126171
+ if (adapter?.acknowledge === void 0) return;
126172
+ try {
126173
+ const ackId = await adapter.acknowledge(binding, messageId);
126174
+ if (ackId === null) return;
126175
+ const pending = acknowledgements.get(threadId) ?? [];
126176
+ pending.push({ messageId, ackId });
126177
+ acknowledgements.set(threadId, pending);
126178
+ host.log.info("chat acknowledged a message", { threadId, messageId });
126179
+ } catch (error) {
126180
+ host.log.warn("chat could not acknowledge the message", { error: describeError(error) });
126181
+ }
126182
+ };
126183
+ const clearWorking = async (binding, threadId) => {
126184
+ const adapter = adapterFor(binding.platform);
126185
+ const pending = acknowledgements.get(threadId);
126186
+ if (pending === void 0 || adapter?.clearAcknowledge === void 0) return;
126187
+ acknowledgements.delete(threadId);
126188
+ for (const entry of pending) {
126189
+ try {
126190
+ await adapter.clearAcknowledge(binding, entry.messageId, entry.ackId);
126191
+ } catch (error) {
126192
+ host.log.warn("chat could not clear the acknowledgement", {
126193
+ error: describeError(error)
126194
+ });
126195
+ }
126196
+ }
126197
+ };
126198
+ return { markWorking, clearWorking };
126199
+ };
126200
+
126162
126201
  // src/botConfig.ts
126163
126202
  import {
126164
126203
  PLUGIN_PROXY_DIRECT
126165
126204
  } from "@ycodium-ai/plugin-api";
126166
126205
 
126206
+ // src/attachedThreads.ts
126207
+ var ATTACHED_THREAD_LIMIT = 200;
126208
+ var attachedKey = (chatKey, threadId) => `${chatKey}
126209
+ ${threadId}`;
126210
+ var hasAttached = (state, chatKey, threadId) => state.attached?.[attachedKey(chatKey, threadId)] !== void 0;
126211
+ var attachedOnChat = (state, chatKey, projectId) => Object.values(state.attached ?? {}).filter(
126212
+ (record2) => record2.chatKey === chatKey && record2.projectId === projectId
126213
+ );
126214
+ var nonEmpty = (value) => typeof value === "string" && value !== "" ? value : null;
126215
+ var readAttached = (data) => {
126216
+ const chatKey = nonEmpty(data["chatKey"]);
126217
+ const threadId = nonEmpty(data["threadId"]);
126218
+ const projectId = nonEmpty(data["projectId"]);
126219
+ const attachedAt = nonEmpty(data["attachedAt"]);
126220
+ return chatKey === null || threadId === null || projectId === null || attachedAt === null ? null : { chatKey, threadId, projectId, attachedAt };
126221
+ };
126222
+
126167
126223
  // src/chatState.ts
126168
126224
  var IM_STATE_KEY = "chats";
126169
126225
  var IM_STATE_VERSION = 1;
@@ -126188,7 +126244,8 @@ var DEFAULT_CHAT_RECORD = {
126188
126244
  threadId: null,
126189
126245
  target: { kind: "thread" },
126190
126246
  model: null,
126191
- plan: false
126247
+ plan: false,
126248
+ chatKind: null
126192
126249
  };
126193
126250
  var chatOf = (state, chatKey) => ({
126194
126251
  ...DEFAULT_CHAT_RECORD,
@@ -126232,11 +126289,11 @@ var openQueueOfThread = (state, threadId) => Object.values(queueOf(state)).filte
126232
126289
  (record2) => record2.threadId === threadId && record2.settled === void 0
126233
126290
  );
126234
126291
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
126235
- var nonEmpty = (value) => typeof value === "string" && value !== "" ? value : null;
126292
+ var nonEmpty2 = (value) => typeof value === "string" && value !== "" ? value : null;
126236
126293
  var readTarget = (value) => {
126237
126294
  if (!isRecord(value)) return null;
126238
126295
  if (value["kind"] === "thread") return { kind: "thread" };
126239
- const teamId = nonEmpty(value["teamId"]);
126296
+ const teamId = nonEmpty2(value["teamId"]);
126240
126297
  return value["kind"] === "team" && teamId !== null ? { kind: "team", teamId } : null;
126241
126298
  };
126242
126299
  var readButtons = (value) => {
@@ -126244,8 +126301,8 @@ var readButtons = (value) => {
126244
126301
  const buttons = [];
126245
126302
  for (const entry of value) {
126246
126303
  if (!isRecord(entry)) return null;
126247
- const token = nonEmpty(entry["token"]);
126248
- const decision = nonEmpty(entry["decision"]);
126304
+ const token = nonEmpty2(entry["token"]);
126305
+ const decision = nonEmpty2(entry["decision"]);
126249
126306
  const label = typeof entry["label"] === "string" ? entry["label"] : null;
126250
126307
  if (token === null || decision === null || label === null) return null;
126251
126308
  buttons.push({ token, label, decision });
@@ -126257,8 +126314,8 @@ var readQuestionOptions = (value) => {
126257
126314
  const options = [];
126258
126315
  for (const entry of value) {
126259
126316
  if (!isRecord(entry)) return null;
126260
- const token = nonEmpty(entry["token"]);
126261
- const optionValue = nonEmpty(entry["value"]);
126317
+ const token = nonEmpty2(entry["token"]);
126318
+ const optionValue = nonEmpty2(entry["value"]);
126262
126319
  if (token === null || optionValue === null || typeof entry["label"] !== "string") return null;
126263
126320
  const planAction = entry["planAction"];
126264
126321
  options.push({
@@ -126276,10 +126333,10 @@ var readQuestionItems = (value) => {
126276
126333
  const items = [];
126277
126334
  for (const entry of value) {
126278
126335
  if (!isRecord(entry)) return null;
126279
- const id = nonEmpty(entry["id"]);
126336
+ const id = nonEmpty2(entry["id"]);
126280
126337
  const options = readQuestionOptions(entry["options"]);
126281
126338
  if (id === null || options === null || typeof entry["question"] !== "string") return null;
126282
- const submitToken = nonEmpty(entry["submitToken"]);
126339
+ const submitToken = nonEmpty2(entry["submitToken"]);
126283
126340
  items.push({
126284
126341
  id,
126285
126342
  header: typeof entry["header"] === "string" ? entry["header"] : "",
@@ -126334,29 +126391,33 @@ var foldImEvent = (state, name, data) => {
126334
126391
  if (!isRecord(data)) return state;
126335
126392
  switch (name) {
126336
126393
  case "chat-updated": {
126337
- const chatKey = nonEmpty(data["chatKey"]);
126394
+ const chatKey = nonEmpty2(data["chatKey"]);
126338
126395
  if (chatKey === null) return state;
126339
126396
  const patch = {};
126340
126397
  if (data["projectId"] === null) patch.projectId = null;
126341
- else if (nonEmpty(data["projectId"]) !== null) patch.projectId = nonEmpty(data["projectId"]);
126398
+ else if (nonEmpty2(data["projectId"]) !== null) patch.projectId = nonEmpty2(data["projectId"]);
126342
126399
  if (data["threadId"] === null) patch.threadId = null;
126343
- else if (nonEmpty(data["threadId"]) !== null) patch.threadId = nonEmpty(data["threadId"]);
126400
+ else if (nonEmpty2(data["threadId"]) !== null) patch.threadId = nonEmpty2(data["threadId"]);
126344
126401
  const target = readTarget(data["target"]);
126345
126402
  if (target !== null) patch.target = target;
126346
126403
  if (data["model"] === null) patch.model = null;
126347
126404
  else if (isRecord(data["model"])) {
126348
- const instanceId = nonEmpty(data["model"]["instanceId"]);
126349
- const model = nonEmpty(data["model"]["model"]);
126405
+ const instanceId = nonEmpty2(data["model"]["instanceId"]);
126406
+ const model = nonEmpty2(data["model"]["model"]);
126350
126407
  if (instanceId !== null && model !== null) patch.model = { instanceId, model };
126351
126408
  }
126352
126409
  if (typeof data["plan"] === "boolean") patch.plan = data["plan"];
126410
+ if (data["chatKind"] === null) patch.chatKind = null;
126411
+ else if (data["chatKind"] === "private" || data["chatKind"] === "group") {
126412
+ patch.chatKind = data["chatKind"];
126413
+ }
126353
126414
  return withChat(state, chatKey, patch);
126354
126415
  }
126355
126416
  case "thread-opened": {
126356
- const chatKey = nonEmpty(data["chatKey"]);
126357
- const threadId = nonEmpty(data["threadId"]);
126358
- const projectId = nonEmpty(data["projectId"]);
126359
- const openedAt = nonEmpty(data["openedAt"]);
126417
+ const chatKey = nonEmpty2(data["chatKey"]);
126418
+ const threadId = nonEmpty2(data["threadId"]);
126419
+ const projectId = nonEmpty2(data["projectId"]);
126420
+ const openedAt = nonEmpty2(data["openedAt"]);
126360
126421
  if (chatKey === null || threadId === null || projectId === null || openedAt === null) {
126361
126422
  return state;
126362
126423
  }
@@ -126369,10 +126430,17 @@ var foldImEvent = (state, name, data) => {
126369
126430
  )
126370
126431
  };
126371
126432
  }
126433
+ case "thread-attached": {
126434
+ const record2 = readAttached(data);
126435
+ if (record2 === null) return state;
126436
+ const key = attachedKey(record2.chatKey, record2.threadId);
126437
+ const attached = { ...state.attached, [key]: record2 };
126438
+ return { ...state, attached: keepNewest(attached, ATTACHED_THREAD_LIMIT) };
126439
+ }
126372
126440
  case "approval-offered": {
126373
- const chatKey = nonEmpty(data["chatKey"]);
126374
- const threadId = nonEmpty(data["threadId"]);
126375
- const requestId = nonEmpty(data["requestId"]);
126441
+ const chatKey = nonEmpty2(data["chatKey"]);
126442
+ const threadId = nonEmpty2(data["threadId"]);
126443
+ const requestId = nonEmpty2(data["requestId"]);
126376
126444
  const options = readButtons(data["options"]);
126377
126445
  if (chatKey === null || threadId === null || requestId === null || options === null) {
126378
126446
  return state;
@@ -126389,7 +126457,7 @@ var foldImEvent = (state, name, data) => {
126389
126457
  };
126390
126458
  }
126391
126459
  case "approval-resolved": {
126392
- const requestId = nonEmpty(data["requestId"]);
126460
+ const requestId = nonEmpty2(data["requestId"]);
126393
126461
  const existing = requestId === null ? void 0 : state.approvals[requestId];
126394
126462
  if (requestId === null || existing === void 0) return state;
126395
126463
  return {
@@ -126398,8 +126466,8 @@ var foldImEvent = (state, name, data) => {
126398
126466
  };
126399
126467
  }
126400
126468
  case "approval-carded": {
126401
- const requestId = nonEmpty(data["requestId"]);
126402
- const cardId = nonEmpty(data["cardId"]);
126469
+ const requestId = nonEmpty2(data["requestId"]);
126470
+ const cardId = nonEmpty2(data["cardId"]);
126403
126471
  const existing = requestId === null ? void 0 : state.approvals[requestId];
126404
126472
  if (requestId === null || cardId === null || existing === void 0) return state;
126405
126473
  return {
@@ -126408,9 +126476,9 @@ var foldImEvent = (state, name, data) => {
126408
126476
  };
126409
126477
  }
126410
126478
  case "question-offered": {
126411
- const chatKey = nonEmpty(data["chatKey"]);
126412
- const threadId = nonEmpty(data["threadId"]);
126413
- const requestId = nonEmpty(data["requestId"]);
126479
+ const chatKey = nonEmpty2(data["chatKey"]);
126480
+ const threadId = nonEmpty2(data["threadId"]);
126481
+ const requestId = nonEmpty2(data["requestId"]);
126414
126482
  const items = readQuestionItems(data["items"]);
126415
126483
  if (chatKey === null || threadId === null || requestId === null || items === null) {
126416
126484
  return state;
@@ -126427,12 +126495,12 @@ var foldImEvent = (state, name, data) => {
126427
126495
  };
126428
126496
  }
126429
126497
  case "question-carded": {
126430
- const cardId = nonEmpty(data["cardId"]);
126498
+ const cardId = nonEmpty2(data["cardId"]);
126431
126499
  if (cardId === null) return state;
126432
126500
  return withQuestionItem(
126433
126501
  state,
126434
- nonEmpty(data["requestId"]),
126435
- nonEmpty(data["questionId"]),
126502
+ nonEmpty2(data["requestId"]),
126503
+ nonEmpty2(data["questionId"]),
126436
126504
  (item) => ({ ...item, cardId })
126437
126505
  );
126438
126506
  }
@@ -126441,8 +126509,8 @@ var foldImEvent = (state, name, data) => {
126441
126509
  if (!Array.isArray(selected)) return state;
126442
126510
  return withQuestionItem(
126443
126511
  state,
126444
- nonEmpty(data["requestId"]),
126445
- nonEmpty(data["questionId"]),
126512
+ nonEmpty2(data["requestId"]),
126513
+ nonEmpty2(data["questionId"]),
126446
126514
  (item) => ({ ...item, selected })
126447
126515
  );
126448
126516
  }
@@ -126452,13 +126520,13 @@ var foldImEvent = (state, name, data) => {
126452
126520
  const label = typeof data["label"] === "string" ? data["label"] : "";
126453
126521
  return withQuestionItem(
126454
126522
  state,
126455
- nonEmpty(data["requestId"]),
126456
- nonEmpty(data["questionId"]),
126523
+ nonEmpty2(data["requestId"]),
126524
+ nonEmpty2(data["questionId"]),
126457
126525
  (item) => ({ ...item, answer, answerLabel: label })
126458
126526
  );
126459
126527
  }
126460
126528
  case "question-resolved": {
126461
- const requestId = nonEmpty(data["requestId"]);
126529
+ const requestId = nonEmpty2(data["requestId"]);
126462
126530
  const existing = requestId === null ? void 0 : questionsOf(state)[requestId];
126463
126531
  if (requestId === null || existing === void 0) return state;
126464
126532
  const ending = data["ending"];
@@ -126475,12 +126543,12 @@ var foldImEvent = (state, name, data) => {
126475
126543
  };
126476
126544
  }
126477
126545
  case "queue-offered": {
126478
- const chatKey = nonEmpty(data["chatKey"]);
126479
- const threadId = nonEmpty(data["threadId"]);
126480
- const intentId = nonEmpty(data["intentId"]);
126481
- const steerToken = nonEmpty(data["steerToken"]);
126482
- const withdrawToken = nonEmpty(data["withdrawToken"]);
126483
- const resumeToken = nonEmpty(data["resumeToken"]);
126546
+ const chatKey = nonEmpty2(data["chatKey"]);
126547
+ const threadId = nonEmpty2(data["threadId"]);
126548
+ const intentId = nonEmpty2(data["intentId"]);
126549
+ const steerToken = nonEmpty2(data["steerToken"]);
126550
+ const withdrawToken = nonEmpty2(data["withdrawToken"]);
126551
+ const resumeToken = nonEmpty2(data["resumeToken"]);
126484
126552
  if (chatKey === null || threadId === null || intentId === null || steerToken === null || withdrawToken === null || resumeToken === null) {
126485
126553
  return state;
126486
126554
  }
@@ -126505,30 +126573,30 @@ var foldImEvent = (state, name, data) => {
126505
126573
  };
126506
126574
  }
126507
126575
  case "queue-carded": {
126508
- const cardId = nonEmpty(data["cardId"]);
126576
+ const cardId = nonEmpty2(data["cardId"]);
126509
126577
  if (cardId === null) return state;
126510
- return withQueueRecord(state, nonEmpty(data["intentId"]), (record2) => ({
126578
+ return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126511
126579
  ...record2,
126512
126580
  cardId
126513
126581
  }));
126514
126582
  }
126515
126583
  case "queue-steering":
126516
- return withQueueRecord(state, nonEmpty(data["intentId"]), (record2) => ({
126584
+ return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126517
126585
  ...record2,
126518
126586
  steering: data["steering"] === true
126519
126587
  }));
126520
126588
  case "queue-settled": {
126521
126589
  const outcome = data["outcome"];
126522
126590
  if (outcome !== "steered" && outcome !== "sent" && outcome !== "removed") return state;
126523
- return withQueueRecord(state, nonEmpty(data["intentId"]), (record2) => ({
126591
+ return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126524
126592
  ...record2,
126525
126593
  settled: outcome
126526
126594
  }));
126527
126595
  }
126528
126596
  case "turn-card-opened": {
126529
- const cardId = nonEmpty(data["cardId"]);
126530
- const chatKey = nonEmpty(data["chatKey"]);
126531
- const threadId = nonEmpty(data["threadId"]);
126597
+ const cardId = nonEmpty2(data["cardId"]);
126598
+ const chatKey = nonEmpty2(data["chatKey"]);
126599
+ const threadId = nonEmpty2(data["threadId"]);
126532
126600
  if (cardId === null || chatKey === null || threadId === null) return state;
126533
126601
  const title = typeof data["title"] === "string" ? data["title"] : "";
126534
126602
  return {
@@ -126540,7 +126608,7 @@ var foldImEvent = (state, name, data) => {
126540
126608
  };
126541
126609
  }
126542
126610
  case "turn-card-closed": {
126543
- const cardId = nonEmpty(data["cardId"]);
126611
+ const cardId = nonEmpty2(data["cardId"]);
126544
126612
  if (cardId === null) return state;
126545
126613
  const { [cardId]: _closed, ...rest } = openTurnCardsOf(state);
126546
126614
  return { ...state, turnCards: rest };
@@ -126557,6 +126625,131 @@ var foldPluginEvent = (pluginId) => (state, event) => {
126557
126625
  return foldImEvent(state, fullName.slice(fullName.lastIndexOf("/") + 1), event.payload["data"]);
126558
126626
  };
126559
126627
 
126628
+ // src/configRead.ts
126629
+ var readString = (row, key) => {
126630
+ const value = row[key];
126631
+ return typeof value === "string" ? value.trim() : "";
126632
+ };
126633
+
126634
+ // src/targetKind.ts
126635
+ var TARGET_KIND_OPTIONS = [
126636
+ { value: "chat", labelKey: "settings.deliveryKind.chat" },
126637
+ { value: "user", labelKey: "settings.deliveryKind.user" },
126638
+ { value: "group", labelKey: "settings.deliveryKind.group" }
126639
+ ];
126640
+ var TARGET_KINDS = new Set(
126641
+ TARGET_KIND_OPTIONS.map((option) => option.value)
126642
+ );
126643
+ var isTargetKind = (value) => TARGET_KINDS.has(value);
126644
+
126645
+ // src/deliveryTargets.ts
126646
+ var DELIVERY_TARGETS_FIELD = "deliveryTargets";
126647
+ var DELIVERY_HTTP_ENABLED_FIELD = "deliveryHttpEnabled";
126648
+ var DELIVERY_HTTP_PORT_FIELD = "deliveryHttpPort";
126649
+ var DELIVERY_HTTP_TOKEN_FIELD = "deliveryHttpToken";
126650
+ var DEFAULT_DELIVERY_HTTP_PORT = 3847;
126651
+ var TARGET_ID_PATTERN = /^[A-Za-z0-9._:@-]{1,128}$/;
126652
+ var isValidTargetId = (value) => TARGET_ID_PATTERN.test(value);
126653
+ var readDeliveryTargets = (config2) => {
126654
+ const table2 = config2[DELIVERY_TARGETS_FIELD];
126655
+ if (typeof table2 !== "object" || table2 === null) return [];
126656
+ return Object.entries(table2).flatMap(([rowId, row]) => {
126657
+ if (typeof row !== "object" || row === null) return [];
126658
+ const record2 = row;
126659
+ const botId = readString(record2, "botId");
126660
+ const targetId = readString(record2, "targetId");
126661
+ const platformChatId = readString(record2, "platformChatId");
126662
+ const kind = readString(record2, "kind") || "chat";
126663
+ if (botId === "" || targetId === "" || platformChatId === "" || !isTargetKind(kind)) return [];
126664
+ if (!isValidTargetId(targetId)) return [];
126665
+ return [
126666
+ {
126667
+ rowId,
126668
+ botId,
126669
+ targetId,
126670
+ name: readString(record2, "name") || targetId,
126671
+ kind,
126672
+ platformChatId
126673
+ }
126674
+ ];
126675
+ });
126676
+ };
126677
+ var findDeliveryTarget = (targets, botId, targetId) => targets.find((t) => t.botId === botId && t.targetId === targetId);
126678
+ var readDeliveryHttpEnabled = (config2) => config2[DELIVERY_HTTP_ENABLED_FIELD] === true || config2[DELIVERY_HTTP_ENABLED_FIELD] === "true";
126679
+ var readDeliveryHttpPort = (config2) => {
126680
+ const raw = config2[DELIVERY_HTTP_PORT_FIELD];
126681
+ const n = typeof raw === "number" ? raw : typeof raw === "string" ? Number.parseInt(raw.trim(), 10) : DEFAULT_DELIVERY_HTTP_PORT;
126682
+ if (!Number.isInteger(n) || n < 1 || n > 65535) return DEFAULT_DELIVERY_HTTP_PORT;
126683
+ return n;
126684
+ };
126685
+ var readDeliveryHttpToken = (config2) => {
126686
+ const value = config2[DELIVERY_HTTP_TOKEN_FIELD];
126687
+ return typeof value === "string" ? value : "";
126688
+ };
126689
+ var buildDeliverySettingsFields = () => [
126690
+ {
126691
+ key: DELIVERY_HTTP_ENABLED_FIELD,
126692
+ kind: "boolean",
126693
+ labelKey: "settings.deliveryHttp.enabled.label",
126694
+ descriptionKey: "settings.deliveryHttp.enabled.description"
126695
+ },
126696
+ {
126697
+ key: DELIVERY_HTTP_PORT_FIELD,
126698
+ kind: "number",
126699
+ labelKey: "settings.deliveryHttp.port.label",
126700
+ descriptionKey: "settings.deliveryHttp.port.description",
126701
+ showWhen: { key: DELIVERY_HTTP_ENABLED_FIELD, equals: ["true"] }
126702
+ },
126703
+ {
126704
+ key: DELIVERY_HTTP_TOKEN_FIELD,
126705
+ kind: "password",
126706
+ labelKey: "settings.deliveryHttp.token.label",
126707
+ descriptionKey: "settings.deliveryHttp.token.description",
126708
+ showWhen: { key: DELIVERY_HTTP_ENABLED_FIELD, equals: ["true"] }
126709
+ },
126710
+ {
126711
+ key: DELIVERY_TARGETS_FIELD,
126712
+ kind: "table",
126713
+ labelKey: "settings.deliveryTargets.label",
126714
+ descriptionKey: "settings.deliveryTargets.description",
126715
+ columns: [
126716
+ {
126717
+ key: "botId",
126718
+ kind: "text",
126719
+ labelKey: "column.deliveryBotId.label",
126720
+ descriptionKey: "column.deliveryBotId.description",
126721
+ placeholderKey: "column.deliveryBotId.placeholder"
126722
+ },
126723
+ {
126724
+ key: "targetId",
126725
+ kind: "text",
126726
+ labelKey: "column.deliveryTargetId.label",
126727
+ descriptionKey: "column.deliveryTargetId.description",
126728
+ placeholderKey: "column.deliveryTargetId.placeholder"
126729
+ },
126730
+ {
126731
+ key: "name",
126732
+ kind: "text",
126733
+ labelKey: "column.deliveryName.label",
126734
+ placeholderKey: "column.deliveryName.placeholder"
126735
+ },
126736
+ {
126737
+ key: "kind",
126738
+ kind: "select",
126739
+ labelKey: "column.deliveryKind.label",
126740
+ options: [...TARGET_KIND_OPTIONS]
126741
+ },
126742
+ {
126743
+ key: "platformChatId",
126744
+ kind: "text",
126745
+ labelKey: "column.deliveryPlatformChatId.label",
126746
+ descriptionKey: "column.deliveryPlatformChatId.description",
126747
+ placeholderKey: "column.deliveryPlatformChatId.placeholder"
126748
+ }
126749
+ ]
126750
+ }
126751
+ ];
126752
+
126560
126753
  // src/botConfig.ts
126561
126754
  var BOTS_FIELD = "bots";
126562
126755
  var REPLY_LANGUAGE_FIELD = "replyLanguage";
@@ -126569,11 +126762,66 @@ var INBOUND_RUNTIME_MODES = /* @__PURE__ */ new Set([
126569
126762
  "full-access"
126570
126763
  ]);
126571
126764
  var DEFAULT_INBOUND_RUNTIME_MODE = "auto-accept-edits";
126765
+ var QUEUE_MODE_OPTIONS = [
126766
+ { value: "queue", labelKey: "settings.queueMode.queue" },
126767
+ { value: "steer", labelKey: "settings.queueMode.steer" }
126768
+ ];
126769
+ var QUEUE_MODES = new Set(QUEUE_MODE_OPTIONS.map((option) => option.value));
126770
+ var DEFAULT_QUEUE_MODE = "queue";
126771
+ var ACCESS_MODE_OPTIONS = [
126772
+ { value: "allowlist", labelKey: "settings.accessMode.allowlist" },
126773
+ { value: "open", labelKey: "settings.accessMode.open" }
126774
+ ];
126775
+ var ACCESS_MODES = new Set(
126776
+ ACCESS_MODE_OPTIONS.map((option) => option.value)
126777
+ );
126778
+ var DEFAULT_ACCESS_MODE = "allowlist";
126779
+ var LARK_PANEL_MODE_OPTIONS = [
126780
+ { value: "default", labelKey: "settings.larkPanelMode.default" },
126781
+ { value: "minimal", labelKey: "settings.larkPanelMode.minimal" }
126782
+ ];
126783
+ var LARK_PANEL_MODES = new Set(
126784
+ LARK_PANEL_MODE_OPTIONS.map((option) => option.value)
126785
+ );
126786
+ var DEFAULT_LARK_PANEL_MODE = "default";
126572
126787
  var chatKeyOf = (binding) => `${binding.rowId}:${binding.chatId}`;
126573
- var readString = (row, key) => {
126574
- const value = row[key];
126575
- return typeof value === "string" ? value.trim() : "";
126788
+ var parseQueueMode = (value) => QUEUE_MODES.has(value) ? value : DEFAULT_QUEUE_MODE;
126789
+ var parseAccessMode = (value) => ACCESS_MODES.has(value) ? value : DEFAULT_ACCESS_MODE;
126790
+ var parseLarkPanelMode = (value) => LARK_PANEL_MODES.has(value) ? value : DEFAULT_LARK_PANEL_MODE;
126791
+ var parseOnOff = (value, defaultOn) => {
126792
+ if (value === "on") return true;
126793
+ if (value === "off") return false;
126794
+ return defaultOn;
126576
126795
  };
126796
+ var ROW_ACTION_COMMANDS = {
126797
+ checkConnection: "im.settings.checkConnection",
126798
+ copyDiagnostics: "im.settings.copyDiagnostics",
126799
+ larkRepair: "im.settings.larkRepair"
126800
+ };
126801
+ var BOT_ROW_ACTIONS = [
126802
+ {
126803
+ id: "checkConnection",
126804
+ labelKey: "rowAction.checkConnection.label",
126805
+ descriptionKey: "rowAction.checkConnection.description",
126806
+ command: ROW_ACTION_COMMANDS.checkConnection,
126807
+ fills: []
126808
+ },
126809
+ {
126810
+ id: "copyDiagnostics",
126811
+ labelKey: "rowAction.copyDiagnostics.label",
126812
+ descriptionKey: "rowAction.copyDiagnostics.description",
126813
+ command: ROW_ACTION_COMMANDS.copyDiagnostics,
126814
+ fills: []
126815
+ },
126816
+ {
126817
+ id: "larkRepair",
126818
+ labelKey: "rowAction.larkRepair.label",
126819
+ descriptionKey: "rowAction.larkRepair.description",
126820
+ command: ROW_ACTION_COMMANDS.larkRepair,
126821
+ showWhen: { key: "platform", equals: ["lark"] },
126822
+ fills: []
126823
+ }
126824
+ ];
126577
126825
  var buildSettingsCard = (adapters) => {
126578
126826
  const platformColumn = {
126579
126827
  key: "platform",
@@ -126619,11 +126867,20 @@ var buildSettingsCard = (adapters) => {
126619
126867
  },
126620
126868
  platformColumn,
126621
126869
  ...credentialColumns,
126870
+ {
126871
+ key: "accessMode",
126872
+ kind: "select",
126873
+ labelKey: "column.accessMode.label",
126874
+ descriptionKey: "column.accessMode.description",
126875
+ options: [...ACCESS_MODE_OPTIONS]
126876
+ },
126622
126877
  {
126623
126878
  key: "allowedSenders",
126624
126879
  kind: "text",
126625
126880
  labelKey: "column.allowedSenders.label",
126626
- descriptionKey: "column.allowedSenders.description"
126881
+ descriptionKey: "column.allowedSenders.description",
126882
+ // 缺省/空串=allowlist(与 parseAccessMode 一致),仍显示白名单框。
126883
+ showWhen: { key: "accessMode", equals: ["allowlist", ""] }
126627
126884
  },
126628
126885
  {
126629
126886
  // 选项由宿主填,连三态一起——插件只声明「这个格子装的是代理」,宿主因此
@@ -126655,9 +126912,37 @@ var buildSettingsCard = (adapters) => {
126655
126912
  { value: "never", labelKey: "settings.groupMention.never" },
126656
126913
  { value: "ambient", labelKey: "settings.groupMention.ambient" }
126657
126914
  ]
126915
+ },
126916
+ {
126917
+ key: "queueMode",
126918
+ kind: "select",
126919
+ labelKey: "column.queueMode.label",
126920
+ descriptionKey: "column.queueMode.description",
126921
+ options: [...QUEUE_MODE_OPTIONS]
126922
+ },
126923
+ {
126924
+ key: "larkPanelMode",
126925
+ kind: "select",
126926
+ labelKey: "column.larkPanelMode.label",
126927
+ descriptionKey: "column.larkPanelMode.description",
126928
+ showWhen: { key: "platform", equals: ["lark"] },
126929
+ options: [...LARK_PANEL_MODE_OPTIONS]
126930
+ },
126931
+ {
126932
+ key: "wecomNumberedHints",
126933
+ kind: "select",
126934
+ labelKey: "column.wecomNumberedHints.label",
126935
+ descriptionKey: "column.wecomNumberedHints.description",
126936
+ showWhen: { key: "platform", equals: ["wecom"] },
126937
+ options: [
126938
+ { value: "on", labelKey: "settings.onOff.on" },
126939
+ { value: "off", labelKey: "settings.onOff.off" }
126940
+ ]
126658
126941
  }
126659
- ]
126660
- }
126942
+ ],
126943
+ rowActions: [...BOT_ROW_ACTIONS]
126944
+ },
126945
+ ...buildDeliverySettingsFields()
126661
126946
  ]
126662
126947
  };
126663
126948
  };
@@ -126678,10 +126963,14 @@ var readBot = (adapterFor, rowId, row) => {
126678
126963
  displayName: readString(row, "displayName") || adapter.label,
126679
126964
  // 空串=跟机器默认(省略),`direct`=明确不加代理(null),其余就是那个 id。
126680
126965
  proxy: proxyValue === "" ? void 0 : proxyValue === PLUGIN_PROXY_DIRECT ? null : proxyValue,
126966
+ accessMode: parseAccessMode(readString(row, "accessMode")),
126681
126967
  allowedSenders,
126682
126968
  values,
126683
126969
  runtimeMode: INBOUND_RUNTIME_MODES.has(requested) ? requested : DEFAULT_INBOUND_RUNTIME_MODE,
126684
- groupMention: parseGroupMention(readString(row, "groupMention"))
126970
+ groupMention: parseGroupMention(readString(row, "groupMention")),
126971
+ queueMode: parseQueueMode(readString(row, "queueMode")),
126972
+ larkPanelMode: parseLarkPanelMode(readString(row, "larkPanelMode")),
126973
+ wecomNumberedHints: parseOnOff(readString(row, "wecomNumberedHints"), true)
126685
126974
  };
126686
126975
  return adapter.isConfigured(bot) ? bot : null;
126687
126976
  };
@@ -126703,147 +126992,51 @@ var findBindingByChatKey = (bots, state, chatKey) => {
126703
126992
  const bot = bots.find((candidate) => chatKey.startsWith(`${candidate.rowId}:`));
126704
126993
  return bot === void 0 ? void 0 : bindingOf(bot, chatKey.slice(bot.rowId.length + 1), state);
126705
126994
  };
126706
- var isAllowedSender = (bot, sender) => sender !== null && bot.allowedSenders.has(sender);
126707
-
126708
- // src/keyedQueue.ts
126709
- var makeKeyedQueue = (onError) => {
126710
- const tails = /* @__PURE__ */ new Map();
126711
- return (key, work) => {
126712
- const run = (tails.get(key) ?? Promise.resolve()).then(work).catch(onError).then(() => {
126713
- if (tails.get(key) === run) tails.delete(key);
126714
- });
126715
- tails.set(key, run);
126716
- };
126995
+ var isAllowedSender = (bot, sender) => {
126996
+ if (sender === null || sender === "") return false;
126997
+ if (bot.accessMode === "open") return true;
126998
+ return bot.allowedSenders.has(sender);
126717
126999
  };
126718
127000
 
126719
- // src/core.ts
126720
- var describeError = (error) => error instanceof Error ? error.message : String(error);
126721
- var makeImCore = (input) => {
126722
- const { host, t, adapters } = input;
126723
- const adapterFor = (platform) => adapters.find((adapter) => adapter.platform === platform);
126724
- let state = emptyState();
126725
- const stateHandle = input.derivedState({
126726
- key: IM_STATE_KEY,
126727
- version: IM_STATE_VERSION,
126728
- events: ["plugin.event-appended"],
126729
- init: emptyState,
126730
- apply: foldPluginEvent(input.pluginId)
126731
- });
126732
- const ready = stateHandle.get().then(
126733
- (restored) => {
126734
- state = restored;
126735
- },
126736
- (error) => {
126737
- host.log.warn("chat could not restore its saved state", { error: describeError(error) });
126738
- }
126739
- );
126740
- let persisting = Promise.resolve();
126741
- const commit = (name, data) => {
126742
- state = foldImEvent(state, name, data);
126743
- persisting = persisting.then(
126744
- () => host.events.append({ name, aggregate: { kind: "plugin", id: input.pluginId }, data })
126745
- ).then(
126746
- () => void 0,
126747
- (error) => {
126748
- host.log.warn("chat could not save its state", { name, error: describeError(error) });
126749
- }
126750
- );
126751
- };
126752
- const bindChat = (chatKey, threadId) => {
126753
- commit("chat-updated", { chatKey, threadId });
126754
- };
126755
- const outbound = makeKeyedQueue((error) => {
126756
- host.log.warn("chat could not deliver a card", { error: describeError(error) });
126757
- });
126758
- const postImages = async (binding, attachments) => {
126759
- const adapter = adapterFor(binding.platform);
126760
- if (adapter?.sendImage === void 0) return;
126761
- for (const attachment of attachments) {
126762
- if (attachment.type !== "image") continue;
126763
- try {
126764
- const bytes = await host.attachments.read(attachment.id);
126765
- await adapter.sendImage(binding, {
126766
- bytes,
126767
- name: attachment.name,
126768
- mimeType: attachment.mimeType
126769
- });
126770
- } catch (error) {
126771
- host.log.warn("chat could not deliver an image", {
126772
- name: attachment.name,
126773
- error: describeError(error)
126774
- });
126775
- }
126776
- }
126777
- };
126778
- const deliver = async (binding, text3, attachments = []) => {
126779
- await postImages(binding, attachments);
126780
- if (text3.trim() === "") return;
126781
- try {
126782
- await adapterFor(binding.platform)?.sendText(binding, text3);
126783
- } catch (error) {
126784
- host.log.warn("chat could not deliver", { error: describeError(error) });
126785
- }
126786
- };
126787
- const bots = () => readBots(input.config, adapterFor);
126788
- const bindingForChatKey = (chatKey) => findBindingByChatKey(bots(), state, chatKey);
126789
- return {
126790
- host,
126791
- t,
126792
- adapterFor,
126793
- get state() {
126794
- return state;
126795
- },
126796
- ready,
126797
- commit,
126798
- bindChat,
126799
- outbound,
126800
- deliver,
126801
- bots,
126802
- bindingOf: (bot, chatId) => bindingOf(bot, chatId, state),
126803
- bindingForChatKey,
126804
- bindingOfThread: (threadId) => {
126805
- const chatKey = chatOfThread(state, threadId);
126806
- return chatKey === void 0 ? void 0 : bindingForChatKey(chatKey);
126807
- },
126808
- hasPanel: (binding) => adapterFor(binding.platform)?.panel !== void 0
126809
- };
127001
+ // src/numberedReply.ts
127002
+ var optionIndexFrom = (text3, count) => {
127003
+ const trimmed = text3.trim();
127004
+ if (!/^\d+$/.test(trimmed)) return null;
127005
+ const index = Number.parseInt(trimmed, 10);
127006
+ return Number.isInteger(index) && index >= 1 && index <= count ? index : null;
126810
127007
  };
126811
-
126812
- // src/acknowledgements.ts
126813
- var makeAcknowledgements = (core) => {
126814
- const { host, adapterFor } = core;
126815
- const acknowledgements = /* @__PURE__ */ new Map();
126816
- const markWorking = async (binding, threadId, messageId) => {
126817
- const adapter = adapterFor(binding.platform);
126818
- if (adapter?.acknowledge === void 0) return;
126819
- try {
126820
- const ackId = await adapter.acknowledge(binding, messageId);
126821
- if (ackId === null) return;
126822
- const pending = acknowledgements.get(threadId) ?? [];
126823
- pending.push({ messageId, ackId });
126824
- acknowledgements.set(threadId, pending);
126825
- host.log.info("chat acknowledged a message", { threadId, messageId });
126826
- } catch (error) {
126827
- host.log.warn("chat could not acknowledge the message", { error: describeError(error) });
126828
- }
126829
- };
126830
- const clearWorking = async (binding, threadId) => {
126831
- const adapter = adapterFor(binding.platform);
126832
- const pending = acknowledgements.get(threadId);
126833
- if (pending === void 0 || adapter?.clearAcknowledge === void 0) return;
126834
- acknowledgements.delete(threadId);
126835
- for (const entry of pending) {
126836
- try {
126837
- await adapter.clearAcknowledge(binding, entry.messageId, entry.ackId);
126838
- } catch (error) {
126839
- host.log.warn("chat could not clear the acknowledgement", {
126840
- error: describeError(error)
126841
- });
126842
- }
126843
- }
126844
- };
126845
- return { markWorking, clearWorking };
127008
+ var DECISION_WORDS = /* @__PURE__ */ new Map([
127009
+ ["\u6279\u51C6", "accept"],
127010
+ ["\u540C\u610F", "accept"],
127011
+ ["\u5141\u8BB8", "accept"],
127012
+ ["approve", "accept"],
127013
+ ["allow", "accept"],
127014
+ ["yes", "accept"],
127015
+ ["\u62D2\u7EDD", "decline"],
127016
+ ["\u4E0D\u540C\u610F", "decline"],
127017
+ ["deny", "decline"],
127018
+ ["decline", "decline"],
127019
+ ["no", "decline"]
127020
+ ]);
127021
+ var answerByNumber = (text3, options) => {
127022
+ const index = optionIndexFrom(text3, options.length);
127023
+ if (index !== null) return options[index - 1]?.token ?? null;
127024
+ const said = text3.trim().toLowerCase();
127025
+ if (said.length === 0) return null;
127026
+ const byLabel = options.find((option) => option.label.trim().toLowerCase() === said);
127027
+ if (byLabel !== void 0) return byLabel.token;
127028
+ const decision = DECISION_WORDS.get(said);
127029
+ if (decision === void 0) return null;
127030
+ return options.find((option) => option.decision === decision)?.token ?? null;
126846
127031
  };
127032
+ var approvalReplyHint = (t, binding) => binding.platform === "wecom" && binding.wecomNumberedHints ? t("approval.replyHintFull") : t("approval.replyHint");
127033
+ var numberedApprovalText = (request) => [
127034
+ request.body,
127035
+ "",
127036
+ ...request.options.map((option, index) => `${String(index + 1)}. ${option.label}`),
127037
+ "",
127038
+ request.replyHint
127039
+ ].join("\n");
126847
127040
 
126848
127041
  // src/approvals.ts
126849
127042
  var makeApprovals = (core) => {
@@ -126886,7 +127079,7 @@ var makeApprovals = (core) => {
126886
127079
  title: offer.title,
126887
127080
  body: offer.body,
126888
127081
  options: offer.buttons,
126889
- replyHint: t("approval.replyHint")
127082
+ replyHint: approvalReplyHint(t, binding)
126890
127083
  });
126891
127084
  host.log.info("chat asked for an approval", { threadId, requestId: offer.requestId });
126892
127085
  if (cardId === null) return;
@@ -127224,6 +127417,7 @@ var helpText = (t, options = {}) => [
127224
127417
  t("help.new"),
127225
127418
  t("help.sessions"),
127226
127419
  t("help.resume"),
127420
+ t("help.syncNote"),
127227
127421
  t("help.model"),
127228
127422
  t("help.plan"),
127229
127423
  t("help.repo"),
@@ -127531,6 +127725,109 @@ var startConnections = (core, inboundHandlersFor) => {
127531
127725
  return { bots: bots.length, connecting: started.size };
127532
127726
  };
127533
127727
 
127728
+ // src/keyedQueue.ts
127729
+ var makeKeyedQueue = (onError) => {
127730
+ const tails = /* @__PURE__ */ new Map();
127731
+ return (key, work) => {
127732
+ const run = (tails.get(key) ?? Promise.resolve()).then(work).catch(onError).then(() => {
127733
+ if (tails.get(key) === run) tails.delete(key);
127734
+ });
127735
+ tails.set(key, run);
127736
+ };
127737
+ };
127738
+
127739
+ // src/core.ts
127740
+ var makeImCore = (input) => {
127741
+ const { host, t, adapters } = input;
127742
+ const adapterFor = (platform) => adapters.find((adapter) => adapter.platform === platform);
127743
+ let state = emptyState();
127744
+ const stateHandle = input.derivedState({
127745
+ key: IM_STATE_KEY,
127746
+ version: IM_STATE_VERSION,
127747
+ events: ["plugin.event-appended"],
127748
+ init: emptyState,
127749
+ apply: foldPluginEvent(input.pluginId)
127750
+ });
127751
+ const ready = stateHandle.get().then(
127752
+ (restored) => {
127753
+ state = restored;
127754
+ },
127755
+ (error) => {
127756
+ host.log.warn("chat could not restore its saved state", { error: describeError(error) });
127757
+ }
127758
+ );
127759
+ let persisting = Promise.resolve();
127760
+ const commit = (name, data) => {
127761
+ state = foldImEvent(state, name, data);
127762
+ persisting = persisting.then(
127763
+ () => host.events.append({ name, aggregate: { kind: "plugin", id: input.pluginId }, data })
127764
+ ).then(
127765
+ () => void 0,
127766
+ (error) => {
127767
+ host.log.warn("chat could not save its state", { name, error: describeError(error) });
127768
+ }
127769
+ );
127770
+ };
127771
+ const bindChat = (chatKey, threadId) => {
127772
+ commit("chat-updated", { chatKey, threadId });
127773
+ };
127774
+ const outbound = makeKeyedQueue((error) => {
127775
+ host.log.warn("chat could not deliver a card", { error: describeError(error) });
127776
+ });
127777
+ const postImages = async (binding, attachments) => {
127778
+ const adapter = adapterFor(binding.platform);
127779
+ if (adapter?.sendImage === void 0) return;
127780
+ for (const attachment of attachments) {
127781
+ if (attachment.type !== "image") continue;
127782
+ try {
127783
+ const bytes = await host.attachments.read(attachment.id);
127784
+ await adapter.sendImage(binding, {
127785
+ bytes,
127786
+ name: attachment.name,
127787
+ mimeType: attachment.mimeType
127788
+ });
127789
+ } catch (error) {
127790
+ host.log.warn("chat could not deliver an image", {
127791
+ name: attachment.name,
127792
+ error: describeError(error)
127793
+ });
127794
+ }
127795
+ }
127796
+ };
127797
+ const deliver = async (binding, text3, attachments = []) => {
127798
+ await postImages(binding, attachments);
127799
+ if (text3.trim() === "") return;
127800
+ try {
127801
+ await adapterFor(binding.platform)?.sendText(binding, text3);
127802
+ } catch (error) {
127803
+ host.log.warn("chat could not deliver", { error: describeError(error) });
127804
+ }
127805
+ };
127806
+ const bots = () => readBots(input.config, adapterFor);
127807
+ const bindingForChatKey = (chatKey) => findBindingByChatKey(bots(), state, chatKey);
127808
+ return {
127809
+ host,
127810
+ t,
127811
+ adapterFor,
127812
+ get state() {
127813
+ return state;
127814
+ },
127815
+ ready,
127816
+ commit,
127817
+ bindChat,
127818
+ outbound,
127819
+ deliver,
127820
+ bots,
127821
+ bindingOf: (bot, chatId) => bindingOf(bot, chatId, state),
127822
+ bindingForChatKey,
127823
+ bindingOfThread: (threadId) => {
127824
+ const chatKey = chatOfThread(state, threadId);
127825
+ return chatKey === void 0 ? void 0 : bindingForChatKey(chatKey);
127826
+ },
127827
+ hasPanel: (binding) => adapterFor(binding.platform)?.panel !== void 0
127828
+ };
127829
+ };
127830
+
127534
127831
  // src/attachments.ts
127535
127832
  var IMAGE_LIMIT_BYTES = 10 * 1024 * 1024;
127536
127833
  var FILE_LIMIT_BYTES = 25 * 1024 * 1024;
@@ -127777,6 +128074,24 @@ var makeInboundAttachments = (core) => {
127777
128074
  return { pendingByChat, pendingFor, collectResources };
127778
128075
  };
127779
128076
 
128077
+ // src/boundedSeen.ts
128078
+ var makeBoundedSeen = (limit) => {
128079
+ const seen = /* @__PURE__ */ new Set();
128080
+ return {
128081
+ has: (key) => seen.has(key),
128082
+ /** 记下这个键:第一次见返回 true;已经记着返回 false(不重排,最早记的先被忘)。 */
128083
+ claim: (key) => {
128084
+ if (seen.has(key)) return false;
128085
+ seen.add(key);
128086
+ if (seen.size > limit) {
128087
+ const oldest = seen.values().next().value;
128088
+ if (oldest !== void 0) seen.delete(oldest);
128089
+ }
128090
+ return true;
128091
+ }
128092
+ };
128093
+ };
128094
+
127780
128095
  // src/cooldown.ts
127781
128096
  var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
127782
128097
  const lastAt = /* @__PURE__ */ new Map();
@@ -127829,18 +128144,9 @@ var makeInbound = (core, deps) => {
127829
128144
  NOT_ALLOWED_REPLY_KEY_LIMIT
127830
128145
  );
127831
128146
  const projectPromptCooldown = makeCooldown(PROJECT_PROMPT_COOLDOWN_MS, () => Date.now());
127832
- const seen = /* @__PURE__ */ new Set();
127833
- const seenOrder = [];
128147
+ const handledMessages = makeBoundedSeen(SEEN_MESSAGE_LIMIT);
127834
128148
  const alreadyHandled = (target, messageId) => {
127835
- const key = `${chatKeyOf(target)}:${messageId}`;
127836
- if (seen.has(key)) return true;
127837
- seen.add(key);
127838
- seenOrder.push(key);
127839
- if (seenOrder.length > SEEN_MESSAGE_LIMIT) {
127840
- const oldest = seenOrder.shift();
127841
- if (oldest !== void 0) seen.delete(oldest);
127842
- }
127843
- return false;
128149
+ return !handledMessages.claim(`${chatKeyOf(target)}:${messageId}`);
127844
128150
  };
127845
128151
  const turnAwayStranger = (target, sender) => {
127846
128152
  host.log.warn("chat ignored a message from someone not on the allow list", {
@@ -127867,6 +128173,9 @@ var makeInbound = (core, deps) => {
127867
128173
  if (current === void 0) return;
127868
128174
  const arrived = bindingOf2(current, message.chatId);
127869
128175
  if (alreadyHandled(arrived, message.messageId)) return;
128176
+ if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
128177
+ commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
128178
+ }
127870
128179
  if (!isAllowedSender(arrived, message.senderId)) {
127871
128180
  turnAwayStranger(arrived, message.senderId);
127872
128181
  return;
@@ -127883,11 +128192,7 @@ var makeInbound = (core, deps) => {
127883
128192
  return;
127884
128193
  }
127885
128194
  const open = openApprovalOf(core.state, chatKeyOf(arrived));
127886
- const answerToken = open === void 0 ? null : adapterFor(arrived.platform)?.answerByText?.(
127887
- arrived,
127888
- message.text,
127889
- open.options.map(({ label, token }) => ({ label, token }))
127890
- ) ?? null;
128195
+ const answerToken = open === void 0 ? null : adapterFor(arrived.platform)?.answerByText?.(arrived, message.text, open.options) ?? null;
127891
128196
  if (answerToken !== null) {
127892
128197
  void answerByToken(answerToken).catch((error) => {
127893
128198
  host.log.warn("chat could not answer the approval", { error: describeError(error) });
@@ -128541,7 +128846,7 @@ var makeQueueFlow = (host, deps) => {
128541
128846
  }
128542
128847
  return handledWith(null);
128543
128848
  };
128544
- return { noteQueued, refresh, click };
128849
+ return { noteQueued, refresh, click, readExecution: deps.readExecution };
128545
128850
  };
128546
128851
 
128547
128852
  // src/interactions.ts
@@ -128766,24 +129071,10 @@ var TIMEOUT_MS = {
128766
129071
  var RETRY_BACKOFF_MS = 500;
128767
129072
  var RETRY_AFTER_CAP_MS = 1e4;
128768
129073
  var CALL_SPACING_MS = 150;
128769
- var DETAIL_MAX_CHARS = 200;
128770
129074
 
128771
- // src/lark/setup/setupItemSchema.ts
128772
- var SETUP_STEP_IDS = [
128773
- "login",
128774
- "identity",
128775
- "findApp",
128776
- "createApp",
128777
- "enableBot",
128778
- "longConnection",
128779
- "scopes",
128780
- "events",
128781
- "callbacks",
128782
- "publish",
128783
- "secret"
128784
- ];
128785
- var STEP_STATES = ["pending", "running", "done", "skipped", "failed"];
128786
- var SETUP_ITEM_SCHEMA = {
129075
+ // src/flowItemSchema.ts
129076
+ var FLOW_STEP_STATES = ["pending", "running", "done", "skipped", "failed"];
129077
+ var makeFlowItemSchema = (shape) => ({
128787
129078
  type: "object",
128788
129079
  additionalProperties: false,
128789
129080
  required: ["kind"],
@@ -128801,14 +129092,13 @@ var SETUP_ITEM_SCHEMA = {
128801
129092
  additionalProperties: false,
128802
129093
  required: ["id", "labelKey", "state"],
128803
129094
  properties: {
128804
- id: { type: "string", enum: [...SETUP_STEP_IDS] },
129095
+ id: shape.stepId,
128805
129096
  labelKey: { type: "string" },
128806
- state: { type: "string", enum: [...STEP_STATES] }
129097
+ state: { type: "string", enum: [...FLOW_STEP_STATES] }
128807
129098
  }
128808
129099
  }
128809
129100
  },
128810
- // 卡片动作的 done 不往草稿里填:宿主要求带这个字段,内容永远是空对象。
128811
- fills: { type: "object", additionalProperties: false },
129101
+ fills: shape.fills,
128812
129102
  messageKey: { type: "string" },
128813
129103
  warnings: {
128814
129104
  type: "array",
@@ -128826,13 +129116,34 @@ var SETUP_ITEM_SCHEMA = {
128826
129116
  detail: { type: "string" }
128827
129117
  },
128828
129118
  oneOf: [
128829
- { properties: { kind: { const: "qr" } }, required: ["payload", "expiresAt"] },
129119
+ { properties: { kind: { const: "qr" } }, required: [...shape.qrRequired] },
128830
129120
  { properties: { kind: { const: "status" } }, required: ["statusKey"] },
128831
129121
  { properties: { kind: { const: "steps" } }, required: ["steps"] },
128832
- { properties: { kind: { const: "done" } }, required: ["messageKey"] },
129122
+ { properties: { kind: { const: "done" } }, required: [...shape.doneRequired] },
128833
129123
  { properties: { kind: { const: "failed" } }, required: ["reasonKey"] }
128834
129124
  ]
128835
- };
129125
+ });
129126
+
129127
+ // src/lark/setup/setupItemSchema.ts
129128
+ var SETUP_STEP_IDS = [
129129
+ "login",
129130
+ "identity",
129131
+ "findApp",
129132
+ "createApp",
129133
+ "enableBot",
129134
+ "longConnection",
129135
+ "scopes",
129136
+ "events",
129137
+ "callbacks",
129138
+ "publish",
129139
+ "secret"
129140
+ ];
129141
+ var SETUP_ITEM_SCHEMA = makeFlowItemSchema({
129142
+ stepId: { type: "string", enum: [...SETUP_STEP_IDS] },
129143
+ fills: { type: "object", additionalProperties: false },
129144
+ qrRequired: ["payload", "expiresAt"],
129145
+ doneRequired: ["messageKey"]
129146
+ });
128836
129147
 
128837
129148
  // src/lark/setup/saveBot.ts
128838
129149
  var OPEN_ID_PREFIX = "ou_";
@@ -129675,7 +129986,8 @@ var sleep = async (deps) => {
129675
129986
  }
129676
129987
  };
129677
129988
 
129678
- // src/lark/setup/redact.ts
129989
+ // src/redact.ts
129990
+ var DETAIL_MAX_CHARS = 200;
129679
129991
  var MASK = "***";
129680
129992
  var TOKEN_LIKE = /[A-Za-z0-9_=-]{24,}/g;
129681
129993
  var MIN_SECRET_LENGTH = 6;
@@ -130462,7 +130774,8 @@ var fetchTenantToken = async (input) => {
130462
130774
  return { token: body.tenant_access_token, expiresInSeconds: body.expire ?? 0 };
130463
130775
  };
130464
130776
  var sendChatText = async (input) => {
130465
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130777
+ const idType = input.receiveIdType ?? "chat_id";
130778
+ const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=${idType}`, {
130466
130779
  method: "POST",
130467
130780
  headers: {
130468
130781
  authorization: `Bearer ${input.token}`,
@@ -136069,9 +136382,9 @@ var styledText = (text3, style) => {
136069
136382
  if (style?.includes("underline")) out = `<u>${out}</u>`;
136070
136383
  return out;
136071
136384
  };
136072
- var nonEmpty2 = (value) => typeof value === "string" && value !== "";
136385
+ var nonEmpty3 = (value) => typeof value === "string" && value !== "";
136073
136386
  var resourceOf = (element, index) => {
136074
- if (element.tag === "img" && nonEmpty2(element.image_key)) {
136387
+ if (element.tag === "img" && nonEmpty3(element.image_key)) {
136075
136388
  return {
136076
136389
  fileKey: element.image_key,
136077
136390
  kind: "image",
@@ -136079,14 +136392,14 @@ var resourceOf = (element, index) => {
136079
136392
  mimeType: "image/png"
136080
136393
  };
136081
136394
  }
136082
- if ((element.tag === "media" || element.tag === "file") && nonEmpty2(element.file_key)) {
136395
+ if ((element.tag === "media" || element.tag === "file") && nonEmpty3(element.file_key)) {
136083
136396
  const video = element.tag === "media";
136084
- const name = nonEmpty2(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136397
+ const name = nonEmpty3(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136085
136398
  return {
136086
136399
  fileKey: element.file_key,
136087
136400
  kind: "file",
136088
136401
  name,
136089
- mimeType: video && !nonEmpty2(element.file_name) ? "video/mp4" : mimeFromName(name)
136402
+ mimeType: video && !nonEmpty3(element.file_name) ? "video/mp4" : mimeFromName(name)
136090
136403
  };
136091
136404
  }
136092
136405
  return null;
@@ -136098,11 +136411,11 @@ var serializeElement = (element, index, botIds) => {
136098
136411
  return styledText(element.text ?? "", element.style);
136099
136412
  case "a": {
136100
136413
  const label = element.text ?? element.href ?? "";
136101
- return nonEmpty2(element.href) ? `[${label}](${element.href})` : label;
136414
+ return nonEmpty3(element.href) ? `[${label}](${element.href})` : label;
136102
136415
  }
136103
136416
  case "at": {
136104
136417
  if (element.user_id !== void 0 && botIds.has(element.user_id)) return "";
136105
- return nonEmpty2(element.user_name) ? `@${element.user_name}` : "";
136418
+ return nonEmpty3(element.user_name) ? `@${element.user_name}` : "";
136106
136419
  }
136107
136420
  case "code_block":
136108
136421
  return `\`\`\`${element.language ?? ""}
@@ -136142,7 +136455,7 @@ var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
136142
136455
  for (const element of elements) {
136143
136456
  const index = indexOf.get(element);
136144
136457
  const key = element.tag === "img" ? element.image_key : element.file_key;
136145
- if (index !== void 0 && nonEmpty2(key)) {
136458
+ if (index !== void 0 && nonEmpty3(key)) {
136146
136459
  placeholderByKey.set(key, placeholderOf(element, index));
136147
136460
  }
136148
136461
  }
@@ -136469,6 +136782,7 @@ var makeLarkAdapter = (deps) => {
136469
136782
  chatId: message.chatId,
136470
136783
  senderId: message.senderOpenId,
136471
136784
  text: message.text,
136785
+ chatKind: message.chatType === "p2p" ? "private" : "group",
136472
136786
  resources: message.resources.map((resource) => ({
136473
136787
  key: resource.fileKey,
136474
136788
  name: resource.name,
@@ -136486,18 +136800,40 @@ var makeLarkAdapter = (deps) => {
136486
136800
  name: input.name,
136487
136801
  memberOpenIds: input.memberIds
136488
136802
  }),
136489
- sendText: async (binding, text3) => {
136803
+ sendText: async (binding, text3, options) => {
136490
136804
  const token = await tenantToken(binding);
136805
+ const receiveIdType = options?.targetKind === "user" ? "open_id" : "chat_id";
136491
136806
  for (const chunk of splitForLark(text3)) {
136492
136807
  const messageId = await sendChatText({
136493
136808
  http: deps.http,
136494
136809
  token,
136495
136810
  chatId: binding.chatId,
136811
+ receiveIdType,
136496
136812
  text: chunk
136497
136813
  });
136498
136814
  deps.log.info("lark delivered a message", { messageId, bot: binding.displayName });
136499
136815
  }
136500
136816
  },
136817
+ checkConnection: async (bot) => {
136818
+ const { appId, appSecret } = credentials(bot);
136819
+ if (appId === "" || appSecret === "") {
136820
+ return {
136821
+ stage: "credentials",
136822
+ ok: false,
136823
+ detail: "missing app id or secret in saved config"
136824
+ };
136825
+ }
136826
+ try {
136827
+ const { expiresInSeconds } = await fetchTenantToken({ http: deps.http, appId, appSecret });
136828
+ return {
136829
+ stage: "tenant_access_token",
136830
+ ok: true,
136831
+ detail: `token ok; expiresIn=${String(expiresInSeconds)}s`
136832
+ };
136833
+ } catch (error) {
136834
+ return { stage: "tenant_access_token", ok: false, detail: describeError(error) };
136835
+ }
136836
+ },
136501
136837
  sendImage: async (binding, image2) => {
136502
136838
  const token = await tenantToken(binding);
136503
136839
  const imageKey = await uploadImage({
@@ -136630,7 +136966,7 @@ var makeLarkAdapter = (deps) => {
136630
136966
  var en_default = {
136631
136967
  settings: {
136632
136968
  title: "Chat",
136633
- description: "Connect chat bots to Ycodium. One row per bot: create the bot on its platform, paste its credentials, and list the people allowed to use it. For Lark, Connect a Lark bot above does all of that with one scan. Those people message the bot (or add it to a group), pick a project in the chat, and can start work.",
136969
+ description: "Connect chat bots to Ycodium. One row per bot: create the bot on its platform, paste its credentials, and pick an access mode (allowlist / open). In allowlist mode, list the people allowed to use it. For Lark, Connect a Lark bot above does all of that with one scan. Admitted people message the bot (or add it to a group), pick a project in the chat, and can start work.",
136634
136970
  bots: "Bots",
136635
136971
  language: {
136636
136972
  label: "Reply language",
@@ -136648,12 +136984,53 @@ var en_default = {
136648
136984
  always: "Only when it is mentioned",
136649
136985
  never: "Every message",
136650
136986
  ambient: "Every message, except ones aimed at someone else"
136987
+ },
136988
+ deliveryHttp: {
136989
+ enabled: {
136990
+ label: "Enable proactive delivery HTTP",
136991
+ description: "Off by default. When on, listens only on 127.0.0.1 so local scripts can POST botId+targetId text. Do not expose publicly; set a Bearer token when other local processes might call it."
136992
+ },
136993
+ port: {
136994
+ label: "Delivery HTTP port",
136995
+ description: "Bound to 127.0.0.1 only. Default 3847."
136996
+ },
136997
+ token: {
136998
+ label: "Delivery HTTP token",
136999
+ description: "Optional. When set, requests must send Authorization: Bearer <token>."
137000
+ }
137001
+ },
137002
+ deliveryTargets: {
137003
+ label: "Delivery targets",
137004
+ description: "Callers store only botId (settings row id) and targetId. Keep platform chat ids here; do not send them on each request."
137005
+ },
137006
+ deliveryKind: {
137007
+ chat: "Chat",
137008
+ user: "User / DM",
137009
+ group: "Group"
137010
+ },
137011
+ queueMode: {
137012
+ queue: "Queue (default)",
137013
+ steer: "Steer (auto-join the running turn)"
137014
+ },
137015
+ onOff: {
137016
+ on: "On",
137017
+ off: "Off"
137018
+ },
137019
+ larkPanelMode: {
137020
+ default: "Default (all commands)",
137021
+ minimal: "Minimal"
137022
+ },
137023
+ accessMode: {
137024
+ allowlist: "Allowlist only (default)",
137025
+ open: "Allow everyone"
136651
137026
  }
136652
137027
  },
136653
137028
  platform: {
136654
137029
  lark: "Lark",
136655
137030
  telegram: "Telegram",
136656
- wecom: "WeCom"
137031
+ wecom: "WeCom",
137032
+ "wecom-app": "WeCom App",
137033
+ wechat: "WeChat (experimental)"
136657
137034
  },
136658
137035
  column: {
136659
137036
  displayName: {
@@ -136681,7 +137058,7 @@ var en_default = {
136681
137058
  },
136682
137059
  allowedSenders: {
136683
137060
  label: "Allowed people",
136684
- description: "Comma-separated user ids from that platform. Only these people can make the agent work on this machine; while the list is empty, nobody can. Message the bot and it tells you your own id."
137061
+ description: "Comma-separated user ids from that platform. Used only in allowlist mode; while the list is empty, nobody can. Message the bot and it tells you your own id."
136685
137062
  },
136686
137063
  proxy: {
136687
137064
  label: "Proxy",
@@ -136689,11 +137066,86 @@ var en_default = {
136689
137066
  },
136690
137067
  runtimeMode: {
136691
137068
  label: "What a chat message may do",
136692
- description: "People on the allow list start turns from chat, so this is deliberately not full access by default. Whichever mode asks, the request lands in the chat."
137069
+ description: "Admitted people start turns from chat, so this is deliberately not full access by default. Whichever mode asks, the request lands in the chat."
136693
137070
  },
136694
137071
  groupMention: {
136695
137072
  label: "In groups, answer",
136696
137073
  description: "When the bot answers in a group (unset means only when it is mentioned); direct chats always count. WeCom only delivers messages that mention the bot, so this changes nothing there."
137074
+ },
137075
+ deliveryBotId: {
137076
+ label: "Bot ID",
137077
+ description: "The settings-table row id for that bot (opaque).",
137078
+ placeholder: "bot row id"
137079
+ },
137080
+ deliveryTargetId: {
137081
+ label: "Target ID",
137082
+ description: "Caller alias unique under that bot; immutable after save.",
137083
+ placeholder: "release-alerts"
137084
+ },
137085
+ deliveryName: {
137086
+ label: "Display name",
137087
+ placeholder: "Release alerts"
137088
+ },
137089
+ deliveryKind: {
137090
+ label: "Kind"
137091
+ },
137092
+ deliveryPlatformChatId: {
137093
+ label: "Platform chat ID",
137094
+ description: "Lark open_id/chat_id, Telegram chat id, WeCom user/group id, etc.",
137095
+ placeholder: "oc_\u2026 / ou_\u2026 / -100\u2026"
137096
+ },
137097
+ queueMode: {
137098
+ label: "New messages while busy",
137099
+ description: "When a turn is already running, how to handle new plain-text messages. Queue waits for the current turn (Steer is still a button); Steer auto-joins the running turn."
137100
+ },
137101
+ larkPanelMode: {
137102
+ label: "Command panel",
137103
+ description: "Which commands /panel (or typing /) lists. Minimal hides low-frequency system items; typing the same command still works."
137104
+ },
137105
+ wecomNumberedHints: {
137106
+ label: "Numbered-reply hints",
137107
+ description: "WeCom has no buttons; approvals and questions use reply numbers. When on, include fuller usage hints."
137108
+ },
137109
+ wecomAppCorpId: {
137110
+ label: "Corp ID",
137111
+ placeholder: "ww\u2026"
137112
+ },
137113
+ wecomAppAgentId: {
137114
+ label: "AgentId",
137115
+ placeholder: "1000002"
137116
+ },
137117
+ wecomAppSecret: {
137118
+ label: "App Secret"
137119
+ },
137120
+ wecomAppToken: {
137121
+ label: "Callback Token"
137122
+ },
137123
+ wecomAppEncodingAESKey: {
137124
+ label: "EncodingAESKey",
137125
+ placeholder: "43 alphanumeric chars"
137126
+ },
137127
+ wecomAppCallbackBaseUrl: {
137128
+ label: "Public callback base URL (optional)",
137129
+ description: "When set, logs print the full callback URL to paste into WeCom \u201CReceive messages \u2192 API receive\u201D. Default listener is 127.0.0.1; use a reverse proxy or tunnel.",
137130
+ placeholder: "https://im.example.com or http://IP:30987"
137131
+ },
137132
+ wecomAppApiBaseUrl: {
137133
+ label: "API proxy base (optional)",
137134
+ description: "Base URL for gettoken / message/send. Leave empty for qyapi.weixin.qq.com. Outbound IP must be on the trusted IP list.",
137135
+ placeholder: "https://qyapi.weixin.qq.com"
137136
+ },
137137
+ wecomAppListenPort: {
137138
+ label: "Local callback port (optional)",
137139
+ description: "Defaults to 30987 on 127.0.0.1. Shared across self-built apps.",
137140
+ placeholder: "30987"
137141
+ },
137142
+ wechatBotToken: {
137143
+ label: "WeChat bot token",
137144
+ description: "bot_token from QR binding. Live iLink login is gated by proposal 0069; filling this in does not connect yet."
137145
+ },
137146
+ accessMode: {
137147
+ label: "Access mode",
137148
+ description: 'Allowlist admits only "Allowed people"; open admits any identifiable sender. New bots default to allowlist.'
136697
137149
  }
136698
137150
  },
136699
137151
  reply: {
@@ -136722,7 +137174,8 @@ var en_default = {
136722
137174
  allow: "Allow",
136723
137175
  deny: "Deny",
136724
137176
  alreadyHandled: "Already handled",
136725
- replyHint: "Reply with the number."
137177
+ replyHint: "Reply with the number, or reply approve/deny.",
137178
+ replyHintFull: "Reply with the number. You can also reply approve/deny; for multi-select questions, comma-separate numbers."
136726
137179
  },
136727
137180
  question: {
136728
137181
  title: "\u2753 Question",
@@ -136815,7 +137268,8 @@ var en_default = {
136815
137268
  plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
136816
137269
  panel: "Send /panel to open the command panel with a button for every command.",
136817
137270
  repo: "\xB7 /repo \u2014 pick the project this chat works on; /repo number or /repo name picks directly",
136818
- group: "\xB7 /group name \u2014 Lark only: the bot creates a group and adds you (bots on other platforms cannot)"
137271
+ group: "\xB7 /group name \u2014 Lark only: the bot creates a group and adds you (bots on other platforms cannot)",
137272
+ syncNote: "\xB7 After a private chat attaches a session, web user text and final assistant text sync here (groups never sync)."
136819
137273
  },
136820
137274
  sessions: {
136821
137275
  title: "Sessions this bot opened for this chat's project ({{count}}):",
@@ -136827,7 +137281,8 @@ var en_default = {
136827
137281
  badge: {
136828
137282
  current: "\u2605 current",
136829
137283
  running: "\u25CF running",
136830
- elsewhere: "in another chat"
137284
+ elsewhere: "in another chat",
137285
+ web: "web"
136831
137286
  },
136832
137287
  age: {
136833
137288
  justNow: "just now",
@@ -136842,7 +137297,10 @@ var en_default = {
136842
137297
  already: "This chat is already on that session.",
136843
137298
  gone: "That session was deleted or archived, so it cannot be resumed.",
136844
137299
  runningElsewhere: "That session is running a turn in another chat \u2014 stop it there first.",
136845
- done: 'Switched to "{{title}}". Send a message to keep going from there.'
137300
+ done: 'Switched to "{{title}}". Send a message to keep going from there.',
137301
+ confirmWeb: '"{{title}}" was opened on the web. Send /resume with the same number again (or /resume confirm) to attach; /resume cancel to abort.',
137302
+ cancelled: "Attach cancelled.",
137303
+ nothingPending: "No web session is waiting for confirm. Use /sessions to see the list."
136846
137304
  },
136847
137305
  model: {
136848
137306
  current: "Model: {{model}}",
@@ -137044,6 +137502,44 @@ var en_default = {
137044
137502
  unsupported: "Bots on this platform cannot create groups. Create the group in the app and add the bot to it.",
137045
137503
  noSender: "I cannot tell who you are on this platform, so I cannot add you to a new group.",
137046
137504
  failed: "Could not create the group. Check that the app is allowed to create groups and add members, then try again."
137505
+ },
137506
+ sync: {
137507
+ webUser: "Web: {{text}}"
137508
+ },
137509
+ rowAction: {
137510
+ checkConnection: {
137511
+ label: "Check connection",
137512
+ description: "Verify platform credentials still work; does not open a session or call a model.",
137513
+ running: "Checking {{rowId}}\u2026",
137514
+ done: "Connection OK",
137515
+ failed: "Connection failed (ref {{ref}})",
137516
+ notSaved: "Save this row first (credentials are read from saved config only)."
137517
+ },
137518
+ copyDiagnostics: {
137519
+ label: "Copy diagnostics",
137520
+ description: "Run a connection probe and produce copy-paste diagnostics (no secrets).",
137521
+ running: "Building diagnostics for {{rowId}}\u2026",
137522
+ done: "Diagnostics ready \u2014 see details below",
137523
+ failed: "Diagnostics failed (ref {{ref}})",
137524
+ notSaved: "Save this row first before copying diagnostics."
137525
+ },
137526
+ diagnostic: {
137527
+ ref: "Ref {{ref}}\n{{detail}}"
137528
+ },
137529
+ larkRepair: {
137530
+ label: "Repair scopes",
137531
+ description: "Verify the Lark app can still mint a token and list scope / publish steps.",
137532
+ step: {
137533
+ scopes: "Confirm message / resource / panel scopes",
137534
+ events: "Confirm long connection and event subscriptions",
137535
+ publish: "Publish if scopes were added",
137536
+ verify: "Verify the app can still mint a tenant token"
137537
+ },
137538
+ done: "Credentials work; add any missing scopes in the Feishu console and publish",
137539
+ hint: "Ref {{ref}}\n{{detail}}\nIf scopes are missing, request them on the Feishu open platform and publish; a chat /repair command will follow.",
137540
+ failed: "Could not verify the Lark app (ref {{ref}})",
137541
+ notLark: "This row is not a saved Lark bot."
137542
+ }
137047
137543
  }
137048
137544
  };
137049
137545
 
@@ -137051,7 +137547,7 @@ var en_default = {
137051
137547
  var zh_CN_default = {
137052
137548
  settings: {
137053
137549
  title: "\u804A\u5929",
137054
- description: "\u628A\u804A\u5929\u673A\u5668\u4EBA\u63A5\u5230 Ycodium\u3002\u4E00\u884C\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5148\u5728\u5BF9\u5E94\u5E73\u53F0\u5EFA\u597D\u673A\u5668\u4EBA\u3001\u628A\u51ED\u636E\u586B\u8FDB\u6765\uFF0C\u518D\u628A\u5141\u8BB8\u4F7F\u7528\u5B83\u7684\u4EBA\u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002\u98DE\u4E66\u53EF\u4EE5\u7528\u4E0A\u9762\u7684\u300C\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA\u300D\uFF0C\u626B\u4E00\u6B21\u7801\u5168\u90E8\u914D\u597D\u3002\u540D\u5355\u91CC\u7684\u4EBA\u79C1\u804A\u673A\u5668\u4EBA\uFF08\u6216\u628A\u5B83\u62C9\u8FDB\u7FA4\uFF09\u3001\u5728\u804A\u5929\u91CC\u9009\u597D\u9879\u76EE\uFF0C\u5C31\u80FD\u6D3E\u6D3B\u3002",
137550
+ description: "\u628A\u804A\u5929\u673A\u5668\u4EBA\u63A5\u5230 Ycodium\u3002\u4E00\u884C\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5148\u5728\u5BF9\u5E94\u5E73\u53F0\u5EFA\u597D\u673A\u5668\u4EBA\u3001\u628A\u51ED\u636E\u586B\u8FDB\u6765\uFF0C\u518D\u9009\u8BBF\u95EE\u6A21\u5F0F\uFF08\u4EC5\u767D\u540D\u5355\uFF0F\u5141\u8BB8\u6240\u6709\u4EBA\uFF09\u3002\u4EC5\u767D\u540D\u5355\u65F6\u628A\u5141\u8BB8\u4F7F\u7528\u5B83\u7684\u4EBA\u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002\u98DE\u4E66\u53EF\u4EE5\u7528\u4E0A\u9762\u7684\u300C\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA\u300D\uFF0C\u626B\u4E00\u6B21\u7801\u5168\u90E8\u914D\u597D\u3002\u83B7\u51C6\u7684\u4EBA\u79C1\u804A\u673A\u5668\u4EBA\uFF08\u6216\u628A\u5B83\u62C9\u8FDB\u7FA4\uFF09\u3001\u5728\u804A\u5929\u91CC\u9009\u597D\u9879\u76EE\uFF0C\u5C31\u80FD\u6D3E\u6D3B\u3002",
137055
137551
  bots: "\u673A\u5668\u4EBA",
137056
137552
  language: {
137057
137553
  label: "\u56DE\u8BDD\u8BED\u8A00",
@@ -137069,12 +137565,53 @@ var zh_CN_default = {
137069
137565
  always: "\u88AB @ \u624D\u7406",
137070
137566
  never: "\u6BCF\u53E5\u90FD\u7406",
137071
137567
  ambient: "\u6BCF\u53E5\u90FD\u7406\uFF0C\u660E\u663E\u5BF9\u522B\u4EBA\u8BF4\u7684\u9664\u5916"
137568
+ },
137569
+ deliveryHttp: {
137570
+ enabled: {
137571
+ label: "\u5F00\u542F\u4E3B\u52A8\u6295\u9012 HTTP",
137572
+ description: "\u9ED8\u8BA4\u5173\u95ED\u3002\u5F00\u542F\u540E\u53EA\u5728\u672C\u673A 127.0.0.1 \u76D1\u542C\uFF0C\u4F9B\u811A\u672C\u7528 botId+targetId \u63A8\u6587\u5B57\u3002\u4E0D\u8981\u66B4\u9732\u5230\u516C\u7F51\uFF1B\u9700\u8981\u65F6\u53EF\u518D\u914D Bearer token\u3002"
137573
+ },
137574
+ port: {
137575
+ label: "\u6295\u9012 HTTP \u7AEF\u53E3",
137576
+ description: "\u53EA\u542C 127.0.0.1\u3002\u9ED8\u8BA4 3847\u3002"
137577
+ },
137578
+ token: {
137579
+ label: "\u6295\u9012 HTTP Token",
137580
+ description: "\u53EF\u9009\u3002\u586B\u4E86\u5219\u8BF7\u6C42\u5FC5\u987B\u5E26 Authorization: Bearer <token>\u3002\u540C\u673A\u5176\u5B83\u8FDB\u7A0B\u4E5F\u80FD\u6253 loopback\uFF0C\u5EFA\u8BAE\u751F\u4EA7\u5F00\u542F\u3002"
137581
+ }
137582
+ },
137583
+ deliveryTargets: {
137584
+ label: "\u6295\u9012\u76EE\u6807",
137585
+ description: "\u8C03\u7528\u65B9\u53EA\u4FDD\u5B58 botId\uFF08\u673A\u5668\u4EBA\u884C id\uFF09\u4E0E targetId\u3002\u5E73\u53F0\u539F\u751F\u804A\u5929 id \u5199\u5728\u8FD9\u91CC\uFF0C\u4E0D\u8981\u968F\u6BCF\u6B21\u8BF7\u6C42\u63D0\u4EA4\u3002"
137586
+ },
137587
+ deliveryKind: {
137588
+ chat: "\u804A\u5929",
137589
+ user: "\u7528\u6237\uFF0F\u79C1\u804A",
137590
+ group: "\u7FA4"
137591
+ },
137592
+ queueMode: {
137593
+ queue: "\u6392\u961F\uFF08\u9ED8\u8BA4\uFF09",
137594
+ steer: "\u63D2\u8BDD\uFF08\u81EA\u52A8\u5E76\u5165\u5F53\u524D\u8F6E\uFF09"
137595
+ },
137596
+ onOff: {
137597
+ on: "\u5F00",
137598
+ off: "\u5173"
137599
+ },
137600
+ larkPanelMode: {
137601
+ default: "\u9ED8\u8BA4\uFF08\u5168\u90E8\u547D\u4EE4\uFF09",
137602
+ minimal: "\u7CBE\u7B80"
137603
+ },
137604
+ accessMode: {
137605
+ allowlist: "\u4EC5\u767D\u540D\u5355\uFF08\u9ED8\u8BA4\uFF09",
137606
+ open: "\u5141\u8BB8\u6240\u6709\u4EBA"
137072
137607
  }
137073
137608
  },
137074
137609
  platform: {
137075
137610
  lark: "\u98DE\u4E66",
137076
137611
  telegram: "Telegram",
137077
- wecom: "\u4F01\u4E1A\u5FAE\u4FE1"
137612
+ wecom: "\u4F01\u4E1A\u5FAE\u4FE1",
137613
+ "wecom-app": "\u4F01\u4E1A\u5FAE\u4FE1\u5E94\u7528",
137614
+ wechat: "\u5FAE\u4FE1\uFF08\u5B9E\u9A8C\uFF09"
137078
137615
  },
137079
137616
  column: {
137080
137617
  displayName: {
@@ -137102,7 +137639,7 @@ var zh_CN_default = {
137102
137639
  },
137103
137640
  allowedSenders: {
137104
137641
  label: "\u5141\u8BB8\u7684\u4EBA",
137105
- description: "\u8BE5\u5E73\u53F0\u7684\u7528\u6237 id\uFF0C\u9017\u53F7\u5206\u9694\u3002\u53EA\u6709\u540D\u5355\u91CC\u7684\u4EBA\u80FD\u8BA9 agent \u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u5E72\u6D3B\uFF0C\u7559\u7A7A\u5219\u8C01\u90FD\u4E0D\u80FD\u3002\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u53E5\u8BDD\uFF0C\u5B83\u4F1A\u544A\u8BC9\u4F60\u81EA\u5DF1\u7684 id\u3002"
137642
+ description: "\u8BE5\u5E73\u53F0\u7684\u7528\u6237 id\uFF0C\u9017\u53F7\u5206\u9694\u3002\u4EC5\u5728\u300C\u4EC5\u767D\u540D\u5355\u300D\u6A21\u5F0F\u4E0B\u751F\u6548\uFF1B\u7559\u7A7A\u5219\u8C01\u90FD\u4E0D\u80FD\u3002\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u53E5\u8BDD\uFF0C\u5B83\u4F1A\u544A\u8BC9\u4F60\u81EA\u5DF1\u7684 id\u3002"
137106
137643
  },
137107
137644
  proxy: {
137108
137645
  label: "\u4EE3\u7406",
@@ -137110,11 +137647,86 @@ var zh_CN_default = {
137110
137647
  },
137111
137648
  runtimeMode: {
137112
137649
  label: "\u804A\u5929\u6D88\u606F\u80FD\u505A\u4EC0\u4E48",
137113
- description: "\u540D\u5355\u91CC\u7684\u4EBA\u90FD\u80FD\u4ECE\u804A\u5929\u91CC\u5F00\u4E00\u8F6E\uFF0C\u6240\u4EE5\u9ED8\u8BA4\u6545\u610F\u4E0D\u7ED9\u6EE1\u6743\u9650\u3002\u4E0D\u8BBA\u9009\u54EA\u4E00\u6863\uFF0C\u8981\u62CD\u677F\u7684\u4E8B\u90FD\u4F1A\u6446\u5230\u804A\u5929\u91CC\u3002"
137650
+ description: "\u83B7\u51C6\u7684\u4EBA\u90FD\u80FD\u4ECE\u804A\u5929\u91CC\u5F00\u4E00\u8F6E\uFF0C\u6240\u4EE5\u9ED8\u8BA4\u6545\u610F\u4E0D\u7ED9\u6EE1\u6743\u9650\u3002\u4E0D\u8BBA\u9009\u54EA\u4E00\u6863\uFF0C\u8981\u62CD\u677F\u7684\u4E8B\u90FD\u4F1A\u6446\u5230\u804A\u5929\u91CC\u3002"
137114
137651
  },
137115
137652
  groupMention: {
137116
137653
  label: "\u7FA4\u91CC\u4EC0\u4E48\u65F6\u5019\u7406\u5B83",
137117
137654
  description: "\u673A\u5668\u4EBA\u5728\u7FA4\u91CC\u4EC0\u4E48\u65F6\u5019\u56DE\u5E94\uFF08\u4E0D\u9009\u5C31\u662F\u88AB @ \u624D\u7406\uFF09\uFF1B\u5355\u804A\u6C38\u8FDC\u7406\u3002\u4F01\u4E1A\u5FAE\u4FE1\u53EA\u628A @ \u5230\u673A\u5668\u4EBA\u7684\u7FA4\u6D88\u606F\u63A8\u8FC7\u6765\uFF0C\u8FD9\u4E00\u9879\u5BF9\u5B83\u6CA1\u6709\u6548\u679C\u3002"
137655
+ },
137656
+ deliveryBotId: {
137657
+ label: "Bot ID",
137658
+ description: "\u8BBE\u7F6E\u8868\u91CC\u8BE5\u673A\u5668\u4EBA\u90A3\u4E00\u884C\u7684 id\uFF08\u4E0D\u900F\u660E\u5B57\u7B26\u4E32\uFF09\u3002",
137659
+ placeholder: "\u673A\u5668\u4EBA\u884C id"
137660
+ },
137661
+ deliveryTargetId: {
137662
+ label: "Target ID",
137663
+ description: "\u540C\u4E00\u673A\u5668\u4EBA\u4E0B\u552F\u4E00\u7684\u522B\u540D\uFF1B\u4FDD\u5B58\u540E\u52FF\u6539\u3002",
137664
+ placeholder: "release-alerts"
137665
+ },
137666
+ deliveryName: {
137667
+ label: "\u663E\u793A\u540D",
137668
+ placeholder: "\u53D1\u5E03\u544A\u8B66\u7FA4"
137669
+ },
137670
+ deliveryKind: {
137671
+ label: "\u7C7B\u578B"
137672
+ },
137673
+ deliveryPlatformChatId: {
137674
+ label: "\u5E73\u53F0\u804A\u5929 ID",
137675
+ description: "\u98DE\u4E66 open_id\uFF0Fchat_id\u3001Telegram chat id\u3001\u4F01\u5FAE\u7528\u6237\uFF0F\u7FA4 id \u7B49\u3002",
137676
+ placeholder: "oc_\u2026 / ou_\u2026 / -100\u2026"
137677
+ },
137678
+ queueMode: {
137679
+ label: "\u5FD9\u65F6\u65B0\u6D88\u606F",
137680
+ description: "\u5F53\u524D\u4EFB\u52A1\u8FD8\u5728\u8DD1\u65F6\uFF0C\u65B0\u6765\u7684\u666E\u901A\u6587\u5B57\u600E\u4E48\u5904\u7406\u3002\u6392\u961F\u4F1A\u7B49\u5F53\u524D\u8F6E\u7ED3\u675F\uFF08\u53EF\u70B9\u63D2\u8BDD\uFF09\uFF1B\u63D2\u8BDD\u5219\u81EA\u52A8\u5E76\u5165\u5F53\u524D\u8F6E\uFF0C\u65E0\u9700\u6BCF\u6B21\u70B9\u6309\u94AE\u3002"
137681
+ },
137682
+ larkPanelMode: {
137683
+ label: "\u6307\u4EE4\u9762\u677F",
137684
+ description: "\u804A\u5929\u91CC /panel \u6216\u6253 / \u65F6\u5217\u51FA\u7684\u547D\u4EE4\u8303\u56F4\u3002\u7CBE\u7B80\u9690\u85CF\u4F4E\u9891\u7CFB\u7EDF\u9879\uFF1B\u624B\u52A8\u8F93\u5165\u540C\u4E00\u547D\u4EE4\u4ECD\u53EF\u7528\u3002"
137685
+ },
137686
+ wecomNumberedHints: {
137687
+ label: "\u7F16\u53F7\u5E94\u7B54\u63D0\u793A",
137688
+ description: "\u4F01\u5FAE\u6CA1\u6709\u6309\u94AE\uFF0C\u5BA1\u6279\u4E0E\u63D0\u95EE\u9760\u56DE\u7F16\u53F7\u3002\u5F00\u542F\u65F6\u9644\u5E26\u66F4\u5B8C\u6574\u7684\u7528\u6CD5\u8BF4\u660E\u3002"
137689
+ },
137690
+ wecomAppCorpId: {
137691
+ label: "\u4F01\u4E1A ID\uFF08CorpID\uFF09",
137692
+ placeholder: "ww\u2026"
137693
+ },
137694
+ wecomAppAgentId: {
137695
+ label: "\u5E94\u7528 AgentId",
137696
+ placeholder: "1000002"
137697
+ },
137698
+ wecomAppSecret: {
137699
+ label: "\u5E94\u7528 Secret"
137700
+ },
137701
+ wecomAppToken: {
137702
+ label: "\u56DE\u8C03 Token"
137703
+ },
137704
+ wecomAppEncodingAESKey: {
137705
+ label: "EncodingAESKey",
137706
+ placeholder: "43 \u4F4D\u5B57\u6BCD\u6216\u6570\u5B57"
137707
+ },
137708
+ wecomAppCallbackBaseUrl: {
137709
+ label: "\u516C\u7F51\u56DE\u8C03\u57FA\u5740\uFF08\u53EF\u9009\uFF09",
137710
+ description: "\u586B\u516C\u7F51\u53EF\u8FBE\u7684 http(s) \u57FA\u5740\u540E\uFF0C\u65E5\u5FD7\u4F1A\u6253\u5370\u5B8C\u6574\u56DE\u8C03 URL\uFF0C\u628A\u5B83\u8D34\u8FDB\u4F01\u5FAE\u300C\u63A5\u6536\u6D88\u606F \u2192 \u8BBE\u7F6E API \u63A5\u6536\u300D\u3002\u4E0D\u586B\u53EA\u663E\u793A\u672C\u673A\u8DEF\u5F84\u3002\u9ED8\u8BA4\u76D1\u542C 127.0.0.1\uFF0C\u9700\u53CD\u4EE3\u6216\u96A7\u9053\u3002",
137711
+ placeholder: "https://im.example.com \u6216 http://IP:30987"
137712
+ },
137713
+ wecomAppApiBaseUrl: {
137714
+ label: "API \u4EE3\u7406\u5730\u5740\uFF08\u53EF\u9009\uFF09",
137715
+ description: "\u51FA\u7AD9\u8C03\u7528 gettoken / message/send \u7684\u57FA\u5740\u3002\u6D77\u5916\u673A\u5668\u53EF\u586B\u53CD\u4EE3\uFF1B\u7559\u7A7A\u76F4\u8FDE qyapi.weixin.qq.com\u3002\u51FA\u7AD9 IP \u987B\u5728\u4F01\u4E1A\u53EF\u4FE1 IP \u5217\u8868\u3002",
137716
+ placeholder: "https://qyapi.weixin.qq.com"
137717
+ },
137718
+ wecomAppListenPort: {
137719
+ label: "\u672C\u673A\u56DE\u8C03\u7AEF\u53E3\uFF08\u53EF\u9009\uFF09",
137720
+ description: "\u9ED8\u8BA4 30987\uFF0C\u53EA\u542C 127.0.0.1\u3002\u591A\u53F0\u81EA\u5EFA\u5E94\u7528\u5171\u7528\u4E00\u4E2A\u7AEF\u53E3\u3002",
137721
+ placeholder: "30987"
137722
+ },
137723
+ wechatBotToken: {
137724
+ label: "\u5FAE\u4FE1\u673A\u5668\u4EBA\u4EE4\u724C",
137725
+ description: "\u626B\u7801\u7ED1\u5B9A\u540E\u5F97\u5230\u7684 bot_token\u3002\u771F\u767B\u5F55\u5C1A\u672A\u5F00\u653E\uFF1A\u586B\u4E86\u4E5F\u4E0D\u4F1A\u8FDE\u4E0A iLink\uFF0C\u89C1\u63D0\u6848 0069 \u5B89\u5168\u8BC4\u5BA1\u3002"
137726
+ },
137727
+ accessMode: {
137728
+ label: "\u8BBF\u95EE\u6A21\u5F0F",
137729
+ description: "\u4EC5\u767D\u540D\u5355\uFF1D\u53EA\u6709\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u80FD\u89E6\u53D1\uFF1B\u5141\u8BB8\u6240\u6709\u4EBA\uFF1D\u5E73\u53F0\u80FD\u8BC6\u522B\u7684\u53D1\u9001\u8005\u5747\u53EF\u3002\u65B0\u673A\u5668\u4EBA\u9ED8\u8BA4\u4EC5\u767D\u540D\u5355\u3002"
137118
137730
  }
137119
137731
  },
137120
137732
  reply: {
@@ -137143,7 +137755,8 @@ var zh_CN_default = {
137143
137755
  allow: "\u5141\u8BB8",
137144
137756
  deny: "\u62D2\u7EDD",
137145
137757
  alreadyHandled: "\u5DF2\u5904\u7406",
137146
- replyHint: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002"
137758
+ replyHint: "\u56DE\u590D\u7F16\u53F7\uFF0C\u6216\u56DE\u590D\u300C\u6279\u51C6\u300D/\u300C\u62D2\u7EDD\u300D\u3002",
137759
+ replyHintFull: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002\u4E5F\u53EF\u56DE\u590D\u300C\u6279\u51C6\u300D/\u300C\u62D2\u7EDD\u300D\uFF1B\u591A\u9009\u63D0\u95EE\u7528\u9017\u53F7\u5206\u9694\u7F16\u53F7\u3002"
137147
137760
  },
137148
137761
  question: {
137149
137762
  title: "\u2753 \u63D0\u95EE",
@@ -137236,7 +137849,8 @@ var zh_CN_default = {
137236
137849
  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",
137237
137850
  panel: "\u53D1 /panel \u6253\u5F00\u53EF\u70B9\u7684\u547D\u4EE4\u9762\u677F\u3002",
137238
137851
  repo: "\xB7 /repo \u2014\u2014 \u9009\u8FD9\u4E2A\u804A\u5929\u505A\u54EA\u4E2A\u9879\u76EE\uFF1B/repo \u7F16\u53F7 \u6216 /repo \u9879\u76EE\u540D \u76F4\u63A5\u9009",
137239
- 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"
137852
+ 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",
137853
+ 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"
137240
137854
  },
137241
137855
  sessions: {
137242
137856
  title: "\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u804A\u5929\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
@@ -137248,7 +137862,8 @@ var zh_CN_default = {
137248
137862
  badge: {
137249
137863
  current: "\u2605 \u5F53\u524D",
137250
137864
  running: "\u25CF \u5728\u8DD1",
137251
- elsewhere: "\u63A5\u5728\u522B\u7684\u804A\u5929"
137865
+ elsewhere: "\u63A5\u5728\u522B\u7684\u804A\u5929",
137866
+ web: "\u7F51\u9875"
137252
137867
  },
137253
137868
  age: {
137254
137869
  justNow: "\u521A\u521A",
@@ -137263,7 +137878,10 @@ var zh_CN_default = {
137263
137878
  already: "\u8FD9\u4E2A\u804A\u5929\u5DF2\u7ECF\u5728\u90A3\u6761\u4F1A\u8BDD\u91CC\u4E86\u3002",
137264
137879
  gone: "\u90A3\u6761\u4F1A\u8BDD\u5DF2\u88AB\u5220\u9664\u6216\u5F52\u6863\uFF0C\u63A5\u4E0D\u4E0A\u4E86\u3002",
137265
137880
  runningElsewhere: "\u90A3\u6761\u4F1A\u8BDD\u6B63\u5728\u522B\u7684\u804A\u5929\u91CC\u8DD1\u7740\u4E00\u8F6E\u2014\u2014\u5148\u5230\u90A3\u8FB9\u505C\u6389\u5B83\u3002",
137266
- done: "\u5DF2\u5207\u5230\u300C{{title}}\u300D\u3002\u53D1\u4E00\u53E5\u8BDD\u5C31\u4ECE\u90A3\u91CC\u63A5\u7740\u804A\u3002"
137881
+ done: "\u5DF2\u5207\u5230\u300C{{title}}\u300D\u3002\u53D1\u4E00\u53E5\u8BDD\u5C31\u4ECE\u90A3\u91CC\u63A5\u7740\u804A\u3002",
137882
+ confirmWeb: "\u300C{{title}}\u300D\u662F\u7F51\u9875\u5F00\u7684\u4F1A\u8BDD\u3002\u518D\u53D1\u4E00\u6B21 /resume \u540C\u4E00\u7F16\u53F7\uFF08\u6216 /resume confirm\uFF09\u786E\u8BA4\u9644\u7740\uFF1B/resume cancel \u53D6\u6D88\u3002",
137883
+ cancelled: "\u5DF2\u53D6\u6D88\u9644\u7740\u3002",
137884
+ nothingPending: "\u6CA1\u6709\u5F85\u786E\u8BA4\u7684\u7F51\u9875\u4F1A\u8BDD\u3002\u7528 /sessions \u770B\u770B\u6709\u54EA\u4E9B\u3002"
137267
137885
  },
137268
137886
  model: {
137269
137887
  current: "\u6A21\u578B\uFF1A{{model}}",
@@ -137465,6 +138083,44 @@ var zh_CN_default = {
137465
138083
  unsupported: "\u8FD9\u4E2A\u5E73\u53F0\u7684\u673A\u5668\u4EBA\u5EFA\u4E0D\u4E86\u7FA4\u3002\u8BF7\u5728 App \u91CC\u81EA\u5DF1\u5EFA\u7FA4\uFF0C\u518D\u628A\u673A\u5668\u4EBA\u62C9\u8FDB\u53BB\u3002",
137466
138084
  noSender: "\u6211\u8BA4\u4E0D\u51FA\u4F60\u5728\u8FD9\u4E2A\u5E73\u53F0\u4E0A\u662F\u8C01\uFF0C\u6CA1\u6CD5\u628A\u4F60\u62C9\u8FDB\u65B0\u7FA4\u3002",
137467
138085
  failed: "\u6CA1\u80FD\u5EFA\u7FA4\u3002\u8BF7\u786E\u8BA4\u8FD9\u4E2A\u5E94\u7528\u6709\u5EFA\u7FA4\u548C\u62C9\u4EBA\u7684\u6743\u9650\uFF0C\u518D\u8BD5\u4E00\u6B21\u3002"
138086
+ },
138087
+ sync: {
138088
+ webUser: "\u7F51\u9875\uFF1A{{text}}"
138089
+ },
138090
+ rowAction: {
138091
+ checkConnection: {
138092
+ label: "\u68C0\u67E5\u8FDE\u63A5",
138093
+ description: "\u9A8C\u8BC1\u5E73\u53F0\u51ED\u636E\u662F\u5426\u4ECD\u53EF\u7528\uFF1B\u4E0D\u4F1A\u5F00\u4F1A\u8BDD\uFF0C\u4E5F\u4E0D\u4F1A\u8C03\u6A21\u578B\u3002",
138094
+ running: "\u6B63\u5728\u68C0\u67E5 {{rowId}} \u2026",
138095
+ done: "\u8FDE\u63A5\u6B63\u5E38",
138096
+ failed: "\u8FDE\u63A5\u5931\u8D25\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138097
+ notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u68C0\u67E5\u8FDE\u63A5\uFF08\u51ED\u636E\u53EA\u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u8BFB\u53D6\uFF09\u3002"
138098
+ },
138099
+ copyDiagnostics: {
138100
+ label: "\u590D\u5236\u8BCA\u65AD",
138101
+ description: "\u8DD1\u4E00\u6B21\u8FDE\u63A5\u63A2\u6D4B\uFF0C\u751F\u6210\u53EF\u590D\u5236\u7684\u8BCA\u65AD\u6587\u672C\uFF08\u4E0D\u542B\u5BC6\u94A5\uFF09\u3002",
138102
+ running: "\u6B63\u5728\u751F\u6210\u8BCA\u65AD {{rowId}} \u2026",
138103
+ done: "\u8BCA\u65AD\u5DF2\u751F\u6210\uFF0C\u89C1\u4E0B\u65B9\u8BE6\u60C5",
138104
+ failed: "\u8BCA\u65AD\u5931\u8D25\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138105
+ notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u590D\u5236\u8BCA\u65AD\u3002"
138106
+ },
138107
+ diagnostic: {
138108
+ ref: "\u53C2\u8003\u53F7 {{ref}}\n{{detail}}"
138109
+ },
138110
+ larkRepair: {
138111
+ label: "\u8865\u5168\u6743\u9650",
138112
+ description: "\u6838\u5BF9\u98DE\u4E66\u5E94\u7528\u4ECD\u80FD\u6362 token\uFF0C\u5E76\u63D0\u793A\u9700\u8981\u8865\u7684\u6743\u9650\u4E0E\u53D1\u5E03\u6B65\u9AA4\u3002",
138113
+ step: {
138114
+ scopes: "\u786E\u8BA4\u6D88\u606F / \u8D44\u6E90 / \u9762\u677F\u7B49\u6743\u9650",
138115
+ events: "\u786E\u8BA4\u957F\u8FDE\u63A5\u4E0E\u4E8B\u4EF6\u8BA2\u9605",
138116
+ publish: "\u5982\u6709\u589E\u91CF\u6743\u9650\uFF0C\u5B8C\u6210\u53D1\u5E03\u5BA1\u6279",
138117
+ verify: "\u9A8C\u8BC1\u5E94\u7528\u4ECD\u53EF\u6362\u53D6 tenant token"
138118
+ },
138119
+ done: "\u51ED\u636E\u53EF\u7528\uFF1B\u82E5\u6743\u9650\u4E0D\u8DB3\u8BF7\u6309\u98DE\u4E66\u63A7\u5236\u53F0\u8865\u9F50\u540E\u53D1\u5E03",
138120
+ hint: "\u53C2\u8003\u53F7 {{ref}}\n{{detail}}\n\u7F3A\u6743\u9650\u65F6\u53EF\u5728\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u5BF9\u8BE5\u5E94\u7528\u589E\u91CF\u7533\u8BF7\u5E76\u53D1\u5E03\uFF1B\u804A\u5929\u4FA7 /repair \u547D\u4EE4\u540E\u7EED\u8865\u9F50\u3002",
138121
+ failed: "\u65E0\u6CD5\u9A8C\u8BC1\u98DE\u4E66\u5E94\u7528\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138122
+ notLark: "\u8FD9\u4E00\u884C\u4E0D\u662F\u5DF2\u4FDD\u5B58\u7684\u98DE\u4E66\u673A\u5668\u4EBA\u3002"
138123
+ }
137468
138124
  }
137469
138125
  };
137470
138126
 
@@ -137752,6 +138408,14 @@ var PANEL_RUN_LINES = Object.fromEntries(
137752
138408
  spec.line
137753
138409
  ])
137754
138410
  );
138411
+ var MINIMAL_HIDDEN = /* @__PURE__ */ new Set([
138412
+ "status",
138413
+ "help",
138414
+ "plan",
138415
+ "team",
138416
+ "thread"
138417
+ ]);
138418
+ var panelCommandsFor = (mode = "default") => mode === "minimal" ? PANEL_COMMANDS.filter((spec) => !MINIMAL_HIDDEN.has(spec.id)) : PANEL_COMMANDS;
137755
138419
 
137756
138420
  // src/sessionList.ts
137757
138421
  var SESSION_LIST_MAX = 20;
@@ -137773,7 +138437,8 @@ var sessionLine = (row, position, now, t) => {
137773
138437
  const badges = [
137774
138438
  row.current ? t("sessions.badge.current") : null,
137775
138439
  row.running ? t("sessions.badge.running") : null,
137776
- row.elsewhere ? t("sessions.badge.elsewhere") : null
138440
+ row.elsewhere ? t("sessions.badge.elsewhere") : null,
138441
+ row.origin === "web" ? t("sessions.badge.web") : null
137777
138442
  ].filter((badge) => badge !== null);
137778
138443
  return [`${String(position)}. ${title}`, age, ...badges, shortId(row.threadId)].filter((part) => part !== "").join(" \xB7 ");
137779
138444
  };
@@ -137855,26 +138520,29 @@ var makePanelController = (deps) => {
137855
138520
  return current === void 0 ? t("repo.needProject") : `${t("repo.currentLine", { title: current.title })}
137856
138521
  ${t("panel.projects.hint")}`;
137857
138522
  };
137858
- const menuCard = (chatKey) => ({
137859
- title: t("panel.title"),
137860
- tone: "menu",
137861
- text: t("panel.hint"),
137862
- groups: PANEL_CATEGORIES.map(
137863
- (category) => ({
137864
- heading: t(`panel.category.${category}`),
137865
- buttons: PANEL_COMMANDS.filter((spec) => spec.category === category).map(
137866
- (spec) => button(
137867
- // 计划模式的按钮把当前状态写在标签上,点了之后标签跟着翻。
137868
- spec.id === "plan" ? t(deps.planOn(chatKey) ? "panel.cmd.planOn" : "panel.cmd.planOff") : t(spec.labelKey),
137869
- spec.action.op,
137870
- spec.action.arg,
137871
- spec.id === "stop"
138523
+ const menuCard = (chatKey) => {
138524
+ const commands = panelCommandsFor(deps.panelMode(chatKey));
138525
+ return {
138526
+ title: t("panel.title"),
138527
+ tone: "menu",
138528
+ text: t("panel.hint"),
138529
+ groups: PANEL_CATEGORIES.map(
138530
+ (category) => ({
138531
+ heading: t(`panel.category.${category}`),
138532
+ buttons: commands.filter((spec) => spec.category === category).map(
138533
+ (spec) => button(
138534
+ // 计划模式的按钮把当前状态写在标签上,点了之后标签跟着翻。
138535
+ spec.id === "plan" ? t(deps.planOn(chatKey) ? "panel.cmd.planOn" : "panel.cmd.planOff") : t(spec.labelKey),
138536
+ spec.action.op,
138537
+ spec.action.arg,
138538
+ spec.id === "stop"
138539
+ )
137872
138540
  )
137873
- )
137874
- })
137875
- ),
137876
- nav: []
137877
- });
138541
+ })
138542
+ ),
138543
+ nav: []
138544
+ };
138545
+ };
137878
138546
  const pickerCard = (input) => {
137879
138547
  const total = pageCount(input.items.length);
137880
138548
  const page = Math.min(Math.max(input.page, 0), total - 1);
@@ -138175,6 +138843,10 @@ var makePanelWiring = (core, deps) => {
138175
138843
  };
138176
138844
  },
138177
138845
  planOn: (chatKey) => chatOf(core.state, chatKey).plan,
138846
+ panelMode: (chatKey) => {
138847
+ const binding = bindingForChatKey(chatKey);
138848
+ return binding?.platform === "lark" ? binding.larkPanelMode : "default";
138849
+ },
138178
138850
  loadSessions: async (chatKey) => {
138179
138851
  const binding = bindingForChatKey(chatKey);
138180
138852
  if (binding === void 0) return [];
@@ -138570,6 +139242,242 @@ var makeReplyCards = (core) => {
138570
139242
  return { turnCards, failureSummaries, stopRequested };
138571
139243
  };
138572
139244
 
139245
+ // src/adapter.ts
139246
+ var ImNotConnectedError = class extends Error {
139247
+ constructor(message) {
139248
+ super(message);
139249
+ this.name = "ImNotConnectedError";
139250
+ }
139251
+ };
139252
+
139253
+ // src/deliveryContract.ts
139254
+ var DELIVERY_SEND_ERROR_CODES = [
139255
+ "bad-request",
139256
+ "unknown-bot",
139257
+ "unknown-target",
139258
+ "bot-not-connected",
139259
+ "delivery-failed"
139260
+ ];
139261
+ var isDeliverySendErrorCode = (value) => typeof value === "string" && DELIVERY_SEND_ERROR_CODES.includes(value);
139262
+ var DeliverySendError = class extends Error {
139263
+ code;
139264
+ constructor(code2, message) {
139265
+ super(message);
139266
+ this.name = "DeliverySendError";
139267
+ this.code = code2;
139268
+ }
139269
+ };
139270
+
139271
+ // src/delivery.ts
139272
+ var makeDeliverySender = (core, config2) => {
139273
+ return async (input) => {
139274
+ const bots = core.bots();
139275
+ const bot = bots.find((candidate) => candidate.rowId === input.botId);
139276
+ if (bot === void 0) {
139277
+ throw new DeliverySendError("unknown-bot", `Unknown bot '${input.botId}'.`);
139278
+ }
139279
+ const target = findDeliveryTarget(readDeliveryTargets(config2), input.botId, input.targetId);
139280
+ if (target === void 0) {
139281
+ throw new DeliverySendError(
139282
+ "unknown-target",
139283
+ `Unknown target '${input.targetId}' for bot '${input.botId}'.`
139284
+ );
139285
+ }
139286
+ const binding = core.bindingOf(bot, target.platformChatId);
139287
+ try {
139288
+ await core.adapterFor(bot.platform)?.sendText(binding, input.text, { targetKind: target.kind });
139289
+ } catch (error) {
139290
+ throw new DeliverySendError(
139291
+ error instanceof ImNotConnectedError ? "bot-not-connected" : "delivery-failed",
139292
+ error instanceof Error ? error.message : String(error)
139293
+ );
139294
+ }
139295
+ return { sent: true };
139296
+ };
139297
+ };
139298
+
139299
+ // src/deliveryHttp.ts
139300
+ import * as NodeCrypto from "node:crypto";
139301
+ import * as NodeHttp from "node:http";
139302
+
139303
+ // src/boundedBody.ts
139304
+ var readBoundedBody = (req, limit) => new Promise((resolve) => {
139305
+ const chunks = [];
139306
+ let size = 0;
139307
+ let settled = false;
139308
+ const finish = (result) => {
139309
+ if (settled) return;
139310
+ settled = true;
139311
+ resolve(result);
139312
+ };
139313
+ req.on("data", (chunk) => {
139314
+ if (settled) return;
139315
+ size += chunk.length;
139316
+ if (size > limit) {
139317
+ finish({ kind: "too-large" });
139318
+ req.destroy();
139319
+ return;
139320
+ }
139321
+ chunks.push(chunk);
139322
+ });
139323
+ req.on("end", () => finish({ kind: "ok", bytes: Buffer.concat(chunks) }));
139324
+ req.on("error", () => finish({ kind: "read-failed" }));
139325
+ req.on("close", () => finish({ kind: "read-failed" }));
139326
+ });
139327
+
139328
+ // src/deliveryHttp.ts
139329
+ var LOOPBACK = "127.0.0.1";
139330
+ var MAX_BODY_BYTES = 1 * 1024 * 1024;
139331
+ var PATH = "/v1/delivery/messages";
139332
+ var statusFor = (code2) => {
139333
+ switch (code2) {
139334
+ case "bad-request":
139335
+ return 400;
139336
+ case "unauthorized":
139337
+ return 401;
139338
+ case "unknown-bot":
139339
+ case "unknown-target":
139340
+ return 404;
139341
+ case "bot-not-connected":
139342
+ return 503;
139343
+ case "delivery-failed":
139344
+ return 502;
139345
+ case "method-not-allowed":
139346
+ return 405;
139347
+ case "unsupported-media-type":
139348
+ return 415;
139349
+ case "payload-too-large":
139350
+ return 413;
139351
+ }
139352
+ };
139353
+ var writeJson = (res, status, body) => {
139354
+ const payload = JSON.stringify(body);
139355
+ res.writeHead(status, {
139356
+ "content-type": "application/json; charset=utf-8",
139357
+ "content-length": Buffer.byteLength(payload),
139358
+ "cache-control": "no-store"
139359
+ });
139360
+ res.end(payload);
139361
+ };
139362
+ var writeError = (res, code2, message) => {
139363
+ writeJson(res, statusFor(code2), { error: { code: code2, message } });
139364
+ };
139365
+ var isLoopbackRemote = (remote) => {
139366
+ if (remote === void 0 || remote === "") return false;
139367
+ return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
139368
+ };
139369
+ var timingSafeEqualString = (a, b) => {
139370
+ const left = Buffer.from(a);
139371
+ const right = Buffer.from(b);
139372
+ if (left.length !== right.length) return false;
139373
+ return NodeCrypto.timingSafeEqual(left, right);
139374
+ };
139375
+ var parseSendBody = (raw) => {
139376
+ let parsed;
139377
+ try {
139378
+ parsed = JSON.parse(raw.toString("utf8"));
139379
+ } catch {
139380
+ return "bad-request";
139381
+ }
139382
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "bad-request";
139383
+ const record2 = parsed;
139384
+ const botId = record2["botId"];
139385
+ const targetId = record2["targetId"];
139386
+ const text3 = record2["text"];
139387
+ const format2 = record2["format"];
139388
+ if (typeof botId !== "string" || botId.trim() === "") return "bad-request";
139389
+ if (typeof targetId !== "string" || targetId.trim() === "") return "bad-request";
139390
+ if (typeof text3 !== "string" || text3.trim() === "") return "bad-request";
139391
+ if (format2 !== void 0 && format2 !== "plain" && format2 !== "markdown") return "bad-request";
139392
+ for (const key of Object.keys(record2)) {
139393
+ if (key !== "botId" && key !== "targetId" && key !== "text" && key !== "format") {
139394
+ return "bad-request";
139395
+ }
139396
+ }
139397
+ return {
139398
+ botId: botId.trim(),
139399
+ targetId: targetId.trim(),
139400
+ text: text3,
139401
+ format: format2 === "markdown" ? "markdown" : format2 === "plain" ? "plain" : void 0
139402
+ };
139403
+ };
139404
+ var startDeliveryHttpServer = async (options) => {
139405
+ const server = NodeHttp.createServer((req, res) => {
139406
+ void (async () => {
139407
+ if (!isLoopbackRemote(req.socket.remoteAddress)) {
139408
+ writeError(res, "unauthorized", "Only loopback clients are accepted.");
139409
+ return;
139410
+ }
139411
+ const url = new URL(req.url ?? "/", `http://${LOOPBACK}`);
139412
+ if (url.pathname !== PATH) {
139413
+ writeError(res, "bad-request", `Unknown path '${url.pathname}'.`);
139414
+ return;
139415
+ }
139416
+ if (req.method !== "POST") {
139417
+ res.setHeader("allow", "POST");
139418
+ writeError(res, "method-not-allowed", "Only POST is accepted.");
139419
+ return;
139420
+ }
139421
+ const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
139422
+ if (contentType !== "application/json") {
139423
+ writeError(res, "unsupported-media-type", "Content-Type must be application/json.");
139424
+ return;
139425
+ }
139426
+ if (options.token !== "") {
139427
+ const header = req.headers.authorization ?? "";
139428
+ const prefix = "Bearer ";
139429
+ if (!header.startsWith(prefix) || !timingSafeEqualString(header.slice(prefix.length), options.token)) {
139430
+ writeError(res, "unauthorized", "Bearer token required.");
139431
+ return;
139432
+ }
139433
+ }
139434
+ const read = await readBoundedBody(req, MAX_BODY_BYTES);
139435
+ if (read.kind !== "ok") {
139436
+ if (read.kind === "too-large") {
139437
+ writeError(res, "payload-too-large", "JSON body exceeds 1 MiB.");
139438
+ } else {
139439
+ writeError(res, "bad-request", "Failed to read body.");
139440
+ }
139441
+ return;
139442
+ }
139443
+ const parsed = parseSendBody(read.bytes);
139444
+ if (typeof parsed === "string") {
139445
+ writeError(res, parsed, "Invalid JSON body.");
139446
+ return;
139447
+ }
139448
+ try {
139449
+ const result = await options.send(parsed);
139450
+ writeJson(res, 200, { sent: result.sent });
139451
+ } catch (error) {
139452
+ const rawCode = error instanceof Error ? error.code : void 0;
139453
+ const code2 = isDeliverySendErrorCode(rawCode) ? rawCode : "delivery-failed";
139454
+ const message = error instanceof Error ? error.message : String(error);
139455
+ options.log?.warn("delivery http send failed", { code: code2, message });
139456
+ writeError(res, code2, message);
139457
+ }
139458
+ })().catch((error) => {
139459
+ options.log?.warn("delivery http handler crashed", {
139460
+ error: error instanceof Error ? error.message : String(error)
139461
+ });
139462
+ if (!res.headersSent) writeError(res, "delivery-failed", "Internal error.");
139463
+ });
139464
+ });
139465
+ await new Promise((resolve, reject) => {
139466
+ server.once("error", reject);
139467
+ server.listen(options.port, LOOPBACK, () => resolve());
139468
+ });
139469
+ const address = server.address();
139470
+ const port = address.port;
139471
+ options.log?.info("delivery http listening", { host: LOOPBACK, port, path: PATH });
139472
+ return {
139473
+ port,
139474
+ origin: `http://${LOOPBACK}:${String(port)}`,
139475
+ close: () => new Promise((resolve, reject) => {
139476
+ server.close((error) => error ? reject(error) : resolve());
139477
+ })
139478
+ };
139479
+ };
139480
+
138573
139481
  // src/sendFile.ts
138574
139482
  var failure = (code2, text3) => ({
138575
139483
  text: text3,
@@ -138711,28 +139619,91 @@ var makeSendFileTool = (deps) => ({
138711
139619
 
138712
139620
  // src/sessions.ts
138713
139621
  var SESSION_SCAN_LIMIT = 50;
139622
+ var WEB_LIST_PAGE = 100;
138714
139623
  var makeSessions = (core) => {
138715
139624
  const { host, t, commit, bindChat, deliver } = core;
138716
139625
  const listedByChat = /* @__PURE__ */ new Map();
139626
+ const pendingAttachByChat = /* @__PURE__ */ new Map();
139627
+ const rowFromThread = (thread, chatKey, origin) => {
139628
+ if (thread.archivedAt != null) return null;
139629
+ return {
139630
+ threadId: thread.threadId,
139631
+ title: thread.title,
139632
+ updatedAt: thread.updatedAt,
139633
+ running: thread.session?.activeTurnId != null,
139634
+ current: chatOf(core.state, chatKey).threadId === thread.threadId,
139635
+ elsewhere: Object.entries(core.state.chats).some(
139636
+ ([otherKey, record2]) => otherKey !== chatKey && record2.threadId === thread.threadId
139637
+ ),
139638
+ origin
139639
+ };
139640
+ };
139641
+ const webShellsForProject = async (projectId) => {
139642
+ const listThreads = host.projections.listThreads;
139643
+ if (listThreads === void 0 || projectId === "") return [];
139644
+ const shells = [];
139645
+ let cursor = void 0;
139646
+ for (let page = 0; page < 5; page += 1) {
139647
+ const result = await listThreads({
139648
+ limit: WEB_LIST_PAGE,
139649
+ includeArchived: false,
139650
+ ...cursor === void 0 || cursor === null ? {} : { cursor }
139651
+ });
139652
+ for (const shell of result.threads) {
139653
+ if (shell.projectId !== projectId) continue;
139654
+ if (shell.archivedAt != null) continue;
139655
+ shells.push({
139656
+ threadId: shell.threadId,
139657
+ title: shell.title,
139658
+ updatedAt: shell.updatedAt,
139659
+ archivedAt: shell.archivedAt,
139660
+ session: {
139661
+ activeTurnId: shell.sessionStatus === "running" ? "running" : null
139662
+ }
139663
+ });
139664
+ }
139665
+ if (result.nextCursor === null) break;
139666
+ cursor = result.nextCursor;
139667
+ }
139668
+ return shells;
139669
+ };
138717
139670
  const sessionRowsFor = async (binding) => {
138718
139671
  const chatKey = chatKeyOf(binding);
139672
+ if (binding.projectId === "") return [];
139673
+ const openedIds = new Set(
139674
+ Object.entries(core.state.opened).filter(([, opened]) => opened.projectId === binding.projectId).map(([threadId]) => threadId)
139675
+ );
138719
139676
  const candidates = Object.entries(core.state.opened).filter(([, opened]) => opened.projectId === binding.projectId).toReversed().slice(0, SESSION_SCAN_LIMIT);
138720
- const threads = await Promise.all(
139677
+ const ownedThreads = await Promise.all(
138721
139678
  candidates.map(([threadId]) => host.projections.getThread(threadId))
138722
139679
  );
138723
139680
  const rows = [];
138724
- for (const thread of threads) {
138725
- if (thread === null || thread.archivedAt != null) continue;
138726
- rows.push({
138727
- threadId: thread.threadId,
138728
- title: thread.title,
138729
- updatedAt: thread.updatedAt,
138730
- running: thread.session?.activeTurnId != null,
138731
- current: chatOf(core.state, chatKey).threadId === thread.threadId,
138732
- elsewhere: Object.entries(core.state.chats).some(
138733
- ([otherKey, record2]) => otherKey !== chatKey && record2.threadId === thread.threadId
138734
- )
138735
- });
139681
+ const seen = /* @__PURE__ */ new Set();
139682
+ for (const thread of ownedThreads) {
139683
+ if (thread === null) continue;
139684
+ const row = rowFromThread(thread, chatKey, "plugin");
139685
+ if (row === null) continue;
139686
+ rows.push(row);
139687
+ seen.add(row.threadId);
139688
+ }
139689
+ const shells = await webShellsForProject(binding.projectId);
139690
+ const listed = new Set(shells.map((shell) => shell.threadId));
139691
+ const attached = await Promise.all(
139692
+ attachedOnChat(core.state, chatKey, binding.projectId).filter(({ threadId }) => !listed.has(threadId)).map(({ threadId }) => host.projections.getThread(threadId))
139693
+ );
139694
+ for (const thread of attached) {
139695
+ if (thread === null || seen.has(thread.threadId) || openedIds.has(thread.threadId)) continue;
139696
+ const row = rowFromThread(thread, chatKey, "web");
139697
+ if (row === null) continue;
139698
+ rows.push(row);
139699
+ seen.add(row.threadId);
139700
+ }
139701
+ for (const shell of shells) {
139702
+ if (seen.has(shell.threadId) || openedIds.has(shell.threadId)) continue;
139703
+ const row = rowFromThread(shell, chatKey, "web");
139704
+ if (row === null) continue;
139705
+ rows.push(row);
139706
+ seen.add(row.threadId);
138736
139707
  }
138737
139708
  return rows.toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt));
138738
139709
  };
@@ -138741,16 +139712,59 @@ var makeSessions = (core) => {
138741
139712
  listedByChat.set(chatKeyOf(binding), listedThreadIds(rows));
138742
139713
  await deliver(binding, formatSessions(rows, Date.now(), t));
138743
139714
  };
139715
+ const bindToSession = async (binding, chatKey, target) => {
139716
+ for (const [otherKey, record2] of Object.entries(core.state.chats)) {
139717
+ if (otherKey !== chatKey && record2.threadId === target.threadId) bindChat(otherKey, null);
139718
+ }
139719
+ if (target.origin === "web" && !hasAttached(core.state, chatKey, target.threadId)) {
139720
+ commit("thread-attached", {
139721
+ chatKey,
139722
+ threadId: target.threadId,
139723
+ projectId: binding.projectId,
139724
+ attachedAt: (/* @__PURE__ */ new Date()).toISOString()
139725
+ });
139726
+ }
139727
+ commit("chat-updated", { chatKey, threadId: target.threadId, target: { kind: "thread" } });
139728
+ pendingAttachByChat.delete(chatKey);
139729
+ listedByChat.delete(chatKey);
139730
+ await deliver(binding, t("resume.done", { title: target.title }));
139731
+ };
138744
139732
  const resumeSession = async (binding, arg) => {
138745
139733
  const chatKey = chatKeyOf(binding);
138746
- const rows = await sessionRowsFor(binding);
138747
- const picked = pickSession(arg, listedByChat.get(chatKey), rows);
138748
- if (picked === null) {
139734
+ const wanted = arg.trim();
139735
+ if (wanted === "") {
138749
139736
  await deliver(binding, t("resume.notFound", { query: arg }));
138750
139737
  return;
138751
139738
  }
138752
- const target = rows.find((row) => row.threadId === picked);
139739
+ if (wanted.toLowerCase() === "cancel") {
139740
+ if (!pendingAttachByChat.has(chatKey)) {
139741
+ await deliver(binding, t("resume.nothingPending"));
139742
+ return;
139743
+ }
139744
+ pendingAttachByChat.delete(chatKey);
139745
+ await deliver(binding, t("resume.cancelled"));
139746
+ return;
139747
+ }
139748
+ const confirming = wanted.toLowerCase() === "confirm";
139749
+ const pending = pendingAttachByChat.get(chatKey);
139750
+ if (confirming && pending === void 0) {
139751
+ await deliver(binding, t("resume.nothingPending"));
139752
+ return;
139753
+ }
139754
+ const rows = await sessionRowsFor(binding);
139755
+ let targetId;
139756
+ if (confirming) {
139757
+ targetId = pending ?? null;
139758
+ } else {
139759
+ targetId = pickSession(wanted, listedByChat.get(chatKey), rows);
139760
+ if (targetId === null) {
139761
+ await deliver(binding, t("resume.notFound", { query: arg }));
139762
+ return;
139763
+ }
139764
+ }
139765
+ const target = rows.find((row) => row.threadId === targetId);
138753
139766
  if (target === void 0) {
139767
+ if (confirming) pendingAttachByChat.delete(chatKey);
138754
139768
  await deliver(binding, t("resume.gone"));
138755
139769
  return;
138756
139770
  }
@@ -138759,6 +139773,7 @@ var makeSessions = (core) => {
138759
139773
  return;
138760
139774
  }
138761
139775
  if (target.current) {
139776
+ if (confirming) pendingAttachByChat.delete(chatKey);
138762
139777
  await deliver(binding, t("resume.already"));
138763
139778
  return;
138764
139779
  }
@@ -138766,12 +139781,12 @@ var makeSessions = (core) => {
138766
139781
  await deliver(binding, t("resume.runningElsewhere"));
138767
139782
  return;
138768
139783
  }
138769
- for (const [otherKey, record2] of Object.entries(core.state.chats)) {
138770
- if (otherKey !== chatKey && record2.threadId === picked) bindChat(otherKey, null);
139784
+ if (target.origin === "web" && !confirming && pending !== target.threadId && !hasAttached(core.state, chatKey, target.threadId)) {
139785
+ pendingAttachByChat.set(chatKey, target.threadId);
139786
+ await deliver(binding, t("resume.confirmWeb", { title: target.title }));
139787
+ return;
138771
139788
  }
138772
- commit("chat-updated", { chatKey, threadId: picked, target: { kind: "thread" } });
138773
- listedByChat.delete(chatKey);
138774
- await deliver(binding, t("resume.done", { title: target.title }));
139789
+ await bindToSession(binding, chatKey, target);
138775
139790
  };
138776
139791
  return { listedByChat, sessionRowsFor, showSessions, resumeSession };
138777
139792
  };
@@ -139299,6 +140314,7 @@ var makeTelegramAdapter = (deps) => {
139299
140314
  chatId: String(message.chat.id),
139300
140315
  senderId: message.from === void 0 ? null : String(message.from.id),
139301
140316
  text: "",
140317
+ chatKind: message.chat.type === "private" ? "private" : "group",
139302
140318
  resources: [],
139303
140319
  unsupportedType: unsupported
139304
140320
  });
@@ -139309,6 +140325,7 @@ var makeTelegramAdapter = (deps) => {
139309
140325
  chatId: String(message.chat.id),
139310
140326
  senderId: message.from === void 0 ? null : String(message.from.id),
139311
140327
  text: text3,
140328
+ chatKind: message.chat.type === "private" ? "private" : "group",
139312
140329
  resources: resource === null ? [] : [
139313
140330
  {
139314
140331
  key: resource.fileId,
@@ -139342,6 +140359,18 @@ var makeTelegramAdapter = (deps) => {
139342
140359
  const api = await apiFor(binding);
139343
140360
  await api.sendMessage(binding.chatId, text3);
139344
140361
  },
140362
+ checkConnection: async (bot) => {
140363
+ if (tokenOf(bot) === "") {
140364
+ return { stage: "credentials", ok: false, detail: "missing bot token in saved config" };
140365
+ }
140366
+ try {
140367
+ const me = await (await apiFor(bot)).getMe();
140368
+ const username = me.username !== void 0 && me.username !== "" ? `@${me.username}` : "(no username)";
140369
+ return { stage: "getMe", ok: true, detail: `username=${username}` };
140370
+ } catch (error) {
140371
+ return { stage: "getMe", ok: false, detail: describeError(error) };
140372
+ }
140373
+ },
139345
140374
  sendImage: async (binding, image2) => {
139346
140375
  const api = await apiFor(binding);
139347
140376
  try {
@@ -139539,6 +140568,22 @@ var makeTurns = (core, deps) => {
139539
140568
  const attachments = [...carried?.attachments ?? [], ...own.attachments];
139540
140569
  const notes = [...carried?.notes ?? [], ...own.notes];
139541
140570
  const userMessageId = crypto.randomUUID();
140571
+ let intentAction;
140572
+ if (binding.queueMode === "steer") {
140573
+ try {
140574
+ const execution = await queueFlow.readExecution(threadId);
140575
+ const activeTurnId = execution?.activeTurnId ?? null;
140576
+ if (activeTurnId !== null) {
140577
+ intentAction = {
140578
+ kind: "steer",
140579
+ source: "automatic",
140580
+ turnId: activeTurnId,
140581
+ commandId: crypto.randomUUID()
140582
+ };
140583
+ }
140584
+ } catch {
140585
+ }
140586
+ }
139542
140587
  try {
139543
140588
  await host.commands.dispatch({
139544
140589
  type: "thread.turn.start",
@@ -139553,12 +140598,14 @@ var makeTurns = (core, deps) => {
139553
140598
  titleSeed: titleFrom(text3 || attachments[0]?.name || "Chat"),
139554
140599
  runtimeMode: binding.runtimeMode,
139555
140600
  interactionMode: interactionModeOf(chatKey),
140601
+ ...intentAction !== void 0 ? { intentAction } : {},
139556
140602
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
139557
140603
  });
139558
140604
  } catch (error) {
139559
140605
  if (carried !== void 0) pendingByChat.set(chatKey, carried);
139560
140606
  throw error;
139561
140607
  }
140608
+ deps.noteInboundUserMessage?.(userMessageId);
139562
140609
  host.log.info("chat started a turn from a message", {
139563
140610
  bot: binding.displayName,
139564
140611
  chatId: binding.chatId,
@@ -139597,8 +140644,303 @@ var makeTurns = (core, deps) => {
139597
140644
  return { threadStillUsable, ensureThread, startTurn, receiveAttachmentsOnly };
139598
140645
  };
139599
140646
 
140647
+ // src/webSync.ts
140648
+ var isWebSyncChat = (state, chatKey, threadId) => {
140649
+ const record2 = chatOf(state, chatKey);
140650
+ return record2.chatKind === "private" && record2.threadId === threadId;
140651
+ };
140652
+ var IM_ORIGIN_LIMIT = 256;
140653
+ var MIRRORED_LIMIT = 256;
140654
+ var makeWebSync = (core) => {
140655
+ const { host, t, deliver, outbound, bindingForChatKey, ready } = core;
140656
+ const imOriginated = makeBoundedSeen(IM_ORIGIN_LIMIT);
140657
+ const mirrored = makeBoundedSeen(MIRRORED_LIMIT);
140658
+ const noteInboundUserMessage = (messageId) => {
140659
+ imOriginated.claim(messageId);
140660
+ };
140661
+ const syncBindingFor = (threadId) => {
140662
+ const chatKey = chatOfThread(core.state, threadId);
140663
+ if (chatKey === void 0) return void 0;
140664
+ if (!isWebSyncChat(core.state, chatKey, threadId)) return void 0;
140665
+ return bindingForChatKey(chatKey);
140666
+ };
140667
+ host.reactor({
140668
+ name: "im.web-sync-user",
140669
+ events: ["thread.message-sent"],
140670
+ handle: async (event) => {
140671
+ const payload = event.payload;
140672
+ if (payload.role !== "user" || payload.streaming !== false) return;
140673
+ if (typeof payload.threadId !== "string" || typeof payload.messageId !== "string") return;
140674
+ if (imOriginated.has(payload.messageId)) return;
140675
+ if (!mirrored.claim(`user:${payload.messageId}`)) return;
140676
+ const { threadId, messageId } = payload;
140677
+ await ready;
140678
+ const binding = syncBindingFor(threadId);
140679
+ if (binding === void 0) return;
140680
+ outbound(chatKeyOf(binding), async () => {
140681
+ try {
140682
+ const projected = await host.projections.getThreadMessage(messageId);
140683
+ const raw = projected?.text?.trim() || (typeof payload.text === "string" ? payload.text.trim() : "");
140684
+ if (raw === "") return;
140685
+ await deliver(binding, t("sync.webUser", { text: raw }));
140686
+ } catch (error) {
140687
+ host.log.warn("web\u2194im sync failed on user message", {
140688
+ messageId,
140689
+ error: describeError(error)
140690
+ });
140691
+ }
140692
+ });
140693
+ }
140694
+ });
140695
+ return { noteInboundUserMessage };
140696
+ };
140697
+
140698
+ // src/diagnostics.ts
140699
+ var PREFIX = {
140700
+ conn: "IM-CONN",
140701
+ msg: "IM-MSG",
140702
+ setup: "IM-SETUP"
140703
+ };
140704
+ var shortSalt = () => {
140705
+ const bytes = new Uint8Array(2);
140706
+ crypto.getRandomValues(bytes);
140707
+ return Array.from(bytes, (b) => b.toString(16).toUpperCase().padStart(2, "0")).join("");
140708
+ };
140709
+ var diagnosticRef = (kind, nowMs = Date.now()) => {
140710
+ const stamp = nowMs.toString(36).toUpperCase();
140711
+ return `${PREFIX[kind]}-${stamp}-${shortSalt()}`;
140712
+ };
140713
+ var formatDiagnosticCopy = (report) => [
140714
+ `ref=${report.ref}`,
140715
+ `ok=${report.ok ? "yes" : "no"}`,
140716
+ `platform=${report.platform}`,
140717
+ `bot=${report.botName}`,
140718
+ `row=${report.rowId}`,
140719
+ `stage=${report.stage}`,
140720
+ `at=${report.at}`,
140721
+ `detail=${report.detail}`
140722
+ ].join("\n");
140723
+
140724
+ // src/settingsRowActions.ts
140725
+ var ROW_PAYLOAD_SCHEMA = {
140726
+ type: "object",
140727
+ required: ["rowId", "values"],
140728
+ properties: {
140729
+ rowId: { type: "string" },
140730
+ values: { type: "object" }
140731
+ },
140732
+ additionalProperties: false
140733
+ };
140734
+ var ROW_ACTION_ITEM_SCHEMA = makeFlowItemSchema({
140735
+ stepId: { type: "string" },
140736
+ fills: { type: "object" },
140737
+ qrRequired: ["payload"],
140738
+ doneRequired: ["messageKey", "fills"]
140739
+ });
140740
+ var botOf = (config2, adapters, rowId) => {
140741
+ const adapterFor = (platform) => adapters.find((adapter) => adapter.platform === platform);
140742
+ return readBots(config2, adapterFor).find((bot) => bot.rowId === rowId);
140743
+ };
140744
+ var reportOf = (bot, stage, ok, detail) => ({
140745
+ ref: diagnosticRef("conn"),
140746
+ platform: bot.platform,
140747
+ botName: bot.displayName,
140748
+ rowId: bot.rowId,
140749
+ stage,
140750
+ ok,
140751
+ detail,
140752
+ at: (/* @__PURE__ */ new Date()).toISOString()
140753
+ });
140754
+ var probe = async (adapters, bot) => {
140755
+ const adapter = adapters.find((candidate) => candidate.platform === bot.platform);
140756
+ if (adapter?.checkConnection === void 0) {
140757
+ return reportOf(bot, "platform", false, `unsupported platform ${bot.platform}`);
140758
+ }
140759
+ const redactor = makeRedactor();
140760
+ for (const column of adapter.columns) {
140761
+ if (column.kind === "password") redactor.register(bot.values[column.key]);
140762
+ }
140763
+ try {
140764
+ const result = await adapter.checkConnection(bot);
140765
+ return reportOf(
140766
+ bot,
140767
+ result.stage,
140768
+ result.ok,
140769
+ result.ok ? result.detail : redactor.redact(result.detail)
140770
+ );
140771
+ } catch (error) {
140772
+ return reportOf(bot, "check", false, redactor.redact(describeError(error)));
140773
+ }
140774
+ };
140775
+ var emitReport = (emit, report, action) => {
140776
+ const detail = action.copyable ? formatDiagnosticCopy(report) : report.detail;
140777
+ if (report.ok) {
140778
+ emit({
140779
+ kind: "done",
140780
+ fills: {},
140781
+ messageKey: `${action.keyPrefix}.done`,
140782
+ warnings: [{ key: "rowAction.diagnostic.ref", params: { ref: report.ref, detail } }]
140783
+ });
140784
+ return;
140785
+ }
140786
+ emit({
140787
+ kind: "failed",
140788
+ reasonKey: `${action.keyPrefix}.failed`,
140789
+ params: { ref: report.ref },
140790
+ detail
140791
+ });
140792
+ };
140793
+ var registerSettingsRowActions = (ctx, adapters) => {
140794
+ const resolveBot = (rowId) => botOf(ctx.config, adapters, rowId);
140795
+ const registerProbeAction = (action) => {
140796
+ ctx.stream({
140797
+ name: action.name,
140798
+ description: action.summary,
140799
+ scope: "operate",
140800
+ payloadSchema: ROW_PAYLOAD_SCHEMA,
140801
+ itemSchema: ROW_ACTION_ITEM_SCHEMA,
140802
+ buffer: { latest: 8 },
140803
+ handle: async (payload, emit) => {
140804
+ const rowId = typeof payload["rowId"] === "string" ? payload["rowId"] : "";
140805
+ emit({ kind: "status", statusKey: `${action.keyPrefix}.running`, params: { rowId } });
140806
+ const bot = resolveBot(rowId);
140807
+ if (bot === void 0) {
140808
+ emit({ kind: "failed", reasonKey: `${action.keyPrefix}.notSaved`, params: { rowId } });
140809
+ return;
140810
+ }
140811
+ emitReport(emit, await probe(adapters, bot), action);
140812
+ }
140813
+ });
140814
+ };
140815
+ registerProbeAction({
140816
+ name: ROW_ACTION_COMMANDS.checkConnection,
140817
+ summary: "Probe an IM bot's platform credentials without calling a model.",
140818
+ keyPrefix: "rowAction.checkConnection",
140819
+ copyable: false
140820
+ });
140821
+ registerProbeAction({
140822
+ name: ROW_ACTION_COMMANDS.copyDiagnostics,
140823
+ summary: "Probe an IM bot and return a copy-paste diagnostic report.",
140824
+ keyPrefix: "rowAction.copyDiagnostics",
140825
+ copyable: true
140826
+ });
140827
+ ctx.stream({
140828
+ name: ROW_ACTION_COMMANDS.larkRepair,
140829
+ description: "Guide Lark scope repair and verify the saved app can still mint a token.",
140830
+ scope: "operate",
140831
+ payloadSchema: ROW_PAYLOAD_SCHEMA,
140832
+ itemSchema: ROW_ACTION_ITEM_SCHEMA,
140833
+ buffer: { latest: 8 },
140834
+ handle: async (payload, emit) => {
140835
+ const rowId = typeof payload["rowId"] === "string" ? payload["rowId"] : "";
140836
+ const send = (item) => emit(item);
140837
+ const ref = diagnosticRef("setup");
140838
+ send({
140839
+ kind: "steps",
140840
+ steps: [
140841
+ { id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: "pending" },
140842
+ { id: "events", labelKey: "rowAction.larkRepair.step.events", state: "pending" },
140843
+ { id: "publish", labelKey: "rowAction.larkRepair.step.publish", state: "pending" },
140844
+ { id: "verify", labelKey: "rowAction.larkRepair.step.verify", state: "running" }
140845
+ ]
140846
+ });
140847
+ const bot = resolveBot(rowId);
140848
+ if (bot === void 0 || bot.platform !== "lark") {
140849
+ send({
140850
+ kind: "failed",
140851
+ reasonKey: "rowAction.larkRepair.notLark",
140852
+ params: { rowId, ref }
140853
+ });
140854
+ return;
140855
+ }
140856
+ const report = await probe(adapters, bot);
140857
+ send({
140858
+ kind: "steps",
140859
+ steps: [
140860
+ { id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: "done" },
140861
+ { id: "events", labelKey: "rowAction.larkRepair.step.events", state: "done" },
140862
+ { id: "publish", labelKey: "rowAction.larkRepair.step.publish", state: "done" },
140863
+ {
140864
+ id: "verify",
140865
+ labelKey: "rowAction.larkRepair.step.verify",
140866
+ state: report.ok ? "done" : "failed"
140867
+ }
140868
+ ]
140869
+ });
140870
+ if (report.ok) {
140871
+ send({
140872
+ kind: "done",
140873
+ fills: {},
140874
+ messageKey: "rowAction.larkRepair.done",
140875
+ warnings: [
140876
+ {
140877
+ key: "rowAction.larkRepair.hint",
140878
+ params: { ref, detail: formatDiagnosticCopy({ ...report, ref }) }
140879
+ }
140880
+ ]
140881
+ });
140882
+ return;
140883
+ }
140884
+ send({
140885
+ kind: "failed",
140886
+ reasonKey: "rowAction.larkRepair.failed",
140887
+ params: { ref },
140888
+ detail: formatDiagnosticCopy({ ...report, ref })
140889
+ });
140890
+ }
140891
+ });
140892
+ };
140893
+
140894
+ // src/wechatApi.ts
140895
+ var WECHAT_TEXT_MAX_CHARS = 1800;
140896
+
140897
+ // src/wechatAdapter.ts
140898
+ var COLUMNS3 = [
140899
+ {
140900
+ key: "wechatBotToken",
140901
+ kind: "password",
140902
+ labelKey: "column.wechatBotToken.label",
140903
+ descriptionKey: "column.wechatBotToken.description"
140904
+ }
140905
+ ];
140906
+ var tokenOf2 = (bot) => bot.values["wechatBotToken"] ?? "";
140907
+ var makeWechatAdapter = (deps) => {
140908
+ return {
140909
+ platform: "wechat",
140910
+ icon: "wechat",
140911
+ // 平台名与设置表的平台选项同一个键,免得默认机器人名与选项写成两种叫法。
140912
+ label: deps.translate("platform.wechat"),
140913
+ columns: COLUMNS3,
140914
+ isConfigured: (bot) => tokenOf2(bot) !== "",
140915
+ connect: (bot, _handlers) => {
140916
+ deps.log.warn("wechat iLink connect is stubbed pending security-review gate (YCO-236)", {
140917
+ bot: bot.displayName,
140918
+ hasToken: tokenOf2(bot) !== "",
140919
+ textLimit: WECHAT_TEXT_MAX_CHARS
140920
+ });
140921
+ return { close: () => void 0 };
140922
+ },
140923
+ sendText: async (binding, _text) => {
140924
+ deps.log.warn("wechat iLink sendText is stubbed pending security-review gate (YCO-236)", {
140925
+ bot: binding.displayName,
140926
+ chatId: binding.chatId
140927
+ });
140928
+ throw new ImNotConnectedError("wechat iLink is not connected yet (stubbed).");
140929
+ },
140930
+ askApproval: async (binding, request) => {
140931
+ deps.log.warn("wechat iLink askApproval is stubbed; approval text not sent", {
140932
+ bot: binding.displayName,
140933
+ chatId: binding.chatId,
140934
+ chars: numberedApprovalText(request).length
140935
+ });
140936
+ return null;
140937
+ },
140938
+ answerByText: (_binding, text3, options) => answerByNumber(text3, options)
140939
+ };
140940
+ };
140941
+
139600
140942
  // src/wecomUpload.ts
139601
- import * as NodeCrypto from "node:crypto";
140943
+ import * as NodeCrypto2 from "node:crypto";
139602
140944
  var WECOM_CHUNK_BYTES = 512 * 1024;
139603
140945
  var WECOM_MAX_CHUNKS = 100;
139604
140946
  var WECOM_MIN_UPLOAD_BYTES = 5;
@@ -139631,7 +140973,7 @@ var uploadWecomMedia = async (input) => {
139631
140973
  filename: clampName(input.filename),
139632
140974
  total_size: bytes.byteLength,
139633
140975
  total_chunks: totalChunks,
139634
- md5: NodeCrypto.createHash("md5").update(bytes).digest("hex")
140976
+ md5: NodeCrypto2.createHash("md5").update(bytes).digest("hex")
139635
140977
  });
139636
140978
  const uploadId = init.upload_id;
139637
140979
  if (typeof uploadId !== "string" || uploadId === "") {
@@ -140003,8 +141345,22 @@ var runWecomClient = (input) => {
140003
141345
  };
140004
141346
 
140005
141347
  // src/wecomMedia.ts
140006
- import * as NodeCrypto2 from "node:crypto";
141348
+ import * as NodeCrypto3 from "node:crypto";
141349
+
141350
+ // src/wecomPadding.ts
140007
141351
  var PADDING_BLOCK = 32;
141352
+ var stripPadding = (plain) => {
141353
+ const pad = plain[plain.byteLength - 1] ?? 0;
141354
+ if (pad < 1 || pad > PADDING_BLOCK || pad > plain.byteLength) {
141355
+ return { reason: "the padding length is out of range" };
141356
+ }
141357
+ for (let index = plain.byteLength - pad; index < plain.byteLength; index += 1) {
141358
+ if (plain[index] !== pad) return { reason: "the padding bytes are not all equal" };
141359
+ }
141360
+ return { bytes: plain.subarray(0, plain.byteLength - pad) };
141361
+ };
141362
+
141363
+ // src/wecomMedia.ts
140008
141364
  var KEY_BYTES = 32;
140009
141365
  var IV_BYTES = 16;
140010
141366
  var AES_BLOCK = 16;
@@ -140020,17 +141376,12 @@ var decryptWecomMedia = (cipher, aesKey) => {
140020
141376
  if (cipher.byteLength === 0 || cipher.byteLength % AES_BLOCK !== 0) {
140021
141377
  throw new WecomDecryptError("the ciphertext is not a whole number of blocks");
140022
141378
  }
140023
- const decipher = NodeCrypto2.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
141379
+ const decipher = NodeCrypto3.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
140024
141380
  decipher.setAutoPadding(false);
140025
141381
  const plain = Buffer.concat([decipher.update(cipher), decipher.final()]);
140026
- const pad = plain[plain.byteLength - 1] ?? 0;
140027
- if (pad < 1 || pad > PADDING_BLOCK || pad > plain.byteLength) {
140028
- throw new WecomDecryptError("the padding length is out of range");
140029
- }
140030
- for (let index = plain.byteLength - pad; index < plain.byteLength; index += 1) {
140031
- if (plain[index] !== pad) throw new WecomDecryptError("the padding bytes are not all equal");
140032
- }
140033
- return new Uint8Array(plain.subarray(0, plain.byteLength - pad));
141382
+ const unpadded = stripPadding(plain);
141383
+ if ("reason" in unpadded) throw new WecomDecryptError(unpadded.reason);
141384
+ return new Uint8Array(unpadded.bytes);
140034
141385
  };
140035
141386
  var decodeQuietly = (value) => {
140036
141387
  try {
@@ -140084,7 +141435,7 @@ var WECOM_CARD_THROTTLE_MS = 400;
140084
141435
  var LIVE_STREAM_LIMIT = 200;
140085
141436
  var BACKOFF_START_MS2 = 1e3;
140086
141437
  var BACKOFF_MAX_MS2 = 6e4;
140087
- var COLUMNS3 = [
141438
+ var COLUMNS4 = [
140088
141439
  { key: "wecomBotId", kind: "text", labelKey: "column.wecomBotId.label" },
140089
141440
  { key: "wecomBotSecret", kind: "password", labelKey: "column.wecomBotSecret.label" }
140090
141441
  ];
@@ -140094,12 +141445,6 @@ var credentials2 = (binding) => ({
140094
141445
  botSecret: binding.values["wecomBotSecret"] ?? "",
140095
141446
  endpoint: normalizeWecomEndpoint(binding.values["wecomEndpoint"] || DEFAULT_WECOM_ENDPOINT)
140096
141447
  });
140097
- var optionIndexFrom = (text3, count) => {
140098
- const trimmed = text3.trim();
140099
- if (!/^\d+$/.test(trimmed)) return null;
140100
- const index = Number.parseInt(trimmed, 10);
140101
- return Number.isInteger(index) && index >= 1 && index <= count ? index : null;
140102
- };
140103
141448
  var makeWecomAdapter = (deps) => {
140104
141449
  const clients = /* @__PURE__ */ new Map();
140105
141450
  const t = deps.translate;
@@ -140142,7 +141487,7 @@ ${kept}`;
140142
141487
  platform: "wecom",
140143
141488
  icon: "wecom",
140144
141489
  label: "WeCom",
140145
- columns: COLUMNS3,
141490
+ columns: COLUMNS4,
140146
141491
  isConfigured: (binding) => {
140147
141492
  const { botId, botSecret } = credentials2(binding);
140148
141493
  return botId !== "" && botSecret !== "";
@@ -140177,6 +141522,8 @@ ${kept}`;
140177
141522
  chatId: message.chatId,
140178
141523
  senderId: message.senderUserId,
140179
141524
  text: message.text,
141525
+ // 群里 @ 到机器人的消息被折算成 single 才放行;真实的聊天类型只留在 conversation。
141526
+ chatKind: message.conversation === "single" ? "private" : "group",
140180
141527
  // 下载地址与密钥打成一个键:核心只管把它原样交回来。
140181
141528
  resources: message.resources.map((resource) => ({
140182
141529
  key: JSON.stringify({ url: resource.url, aesKey: resource.aesKey ?? null }),
@@ -140226,20 +141573,29 @@ ${kept}`;
140226
141573
  sendText: async (binding, text3) => {
140227
141574
  const client = clients.get(binding.rowId);
140228
141575
  if (client === void 0) {
140229
- deps.log.warn("wecom cannot send without a connection", { bot: binding.displayName });
140230
- return;
141576
+ throw new ImNotConnectedError(`wecom bot '${binding.displayName}' has no connection.`);
140231
141577
  }
140232
141578
  await client.sendText(binding.chatId, text3);
140233
141579
  },
141580
+ checkConnection: async (bot) => {
141581
+ const { botId, botSecret, endpoint } = credentials2(bot);
141582
+ if (botId === "" || botSecret === "") {
141583
+ return {
141584
+ stage: "credentials",
141585
+ ok: false,
141586
+ detail: "missing bot id or secret in saved config"
141587
+ };
141588
+ }
141589
+ try {
141590
+ const socket = await deps.connectSocket(endpoint);
141591
+ await socket.close();
141592
+ return { stage: "websocket", ok: true, detail: `endpoint reachable (${endpoint})` };
141593
+ } catch (error) {
141594
+ return { stage: "websocket", ok: false, detail: describeError(error) };
141595
+ }
141596
+ },
140234
141597
  askApproval: async (binding, request) => {
140235
- const lines = [
140236
- request.body,
140237
- "",
140238
- ...request.options.map((option, index) => `${String(index + 1)}. ${option.label}`),
140239
- "",
140240
- request.replyHint
140241
- ];
140242
- await (clients.get(binding.rowId)?.sendText(binding.chatId, lines.join("\n")) ?? Promise.resolve());
141598
+ await (clients.get(binding.rowId)?.sendText(binding.chatId, numberedApprovalText(request)) ?? Promise.resolve());
140243
141599
  return null;
140244
141600
  },
140245
141601
  turnCards: {
@@ -140266,10 +141622,7 @@ ${kept}`;
140266
141622
  // 图 10 MB(只认 png / jpg / jpeg / gif)、文件 20 MB,来自企微官方文档「智能机器人长连接」。
140267
141623
  outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 20 * 1024 * 1024, minBytes: 5 },
140268
141624
  imageMimeTypes: /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif"]),
140269
- answerByText: (_binding, text3, options) => {
140270
- const index = optionIndexFrom(text3, options.length);
140271
- return index === null ? null : options[index - 1]?.token ?? null;
140272
- },
141625
+ answerByText: (_binding, text3, options) => answerByNumber(text3, options),
140273
141626
  fetchResource: async (binding, input) => {
140274
141627
  const parsed = JSON.parse(input.resource.key);
140275
141628
  const media = await fetchWecomMedia({
@@ -140292,6 +141645,783 @@ ${kept}`;
140292
141645
  };
140293
141646
  };
140294
141647
 
141648
+ // src/wecomAppApi.ts
141649
+ var WECOM_APP_DEFAULT_API_BASE = "https://qyapi.weixin.qq.com";
141650
+ var WECOM_APP_TEXT_MAX_BYTES = 2048;
141651
+ var WecomAppApiError = class extends Error {
141652
+ code;
141653
+ providerCode;
141654
+ hint;
141655
+ constructor(code2, message, extras) {
141656
+ super(message);
141657
+ this.name = "WecomAppApiError";
141658
+ this.code = code2;
141659
+ this.providerCode = extras?.providerCode;
141660
+ this.hint = extras?.hint;
141661
+ }
141662
+ };
141663
+ var cleanString = (value) => {
141664
+ if (typeof value !== "string") return null;
141665
+ const trimmed = value.trim();
141666
+ return trimmed === "" ? null : trimmed;
141667
+ };
141668
+ var normalizeApiBaseUrl = (value) => {
141669
+ const raw = cleanString(value);
141670
+ if (raw === null) return WECOM_APP_DEFAULT_API_BASE;
141671
+ if (!/^https?:\/\//iu.test(raw)) {
141672
+ throw new WecomAppApiError("invalid-api-base", "API base must be an http(s) URL.");
141673
+ }
141674
+ return raw.replace(/\/+$/u, "");
141675
+ };
141676
+ var byteLength = (value) => Buffer.byteLength(value, "utf8");
141677
+ var splitUtf8ByBytes = (text3, maxBytes = WECOM_APP_TEXT_MAX_BYTES) => {
141678
+ const value = typeof text3 === "string" ? text3 : "";
141679
+ if (value === "") return [""];
141680
+ const chunks = [];
141681
+ let current = "";
141682
+ let currentBytes = 0;
141683
+ for (const char of value) {
141684
+ const size = byteLength(char);
141685
+ if (currentBytes + size > maxBytes && current.length > 0) {
141686
+ chunks.push(current);
141687
+ current = char;
141688
+ currentBytes = size;
141689
+ continue;
141690
+ }
141691
+ current += char;
141692
+ currentBytes += size;
141693
+ }
141694
+ if (current.length > 0) chunks.push(current);
141695
+ return chunks;
141696
+ };
141697
+ var WecomAppApi = class {
141698
+ corpId;
141699
+ corpSecret;
141700
+ agentId;
141701
+ apiBaseUrl;
141702
+ #fetch;
141703
+ #token = null;
141704
+ #expiresAt = 0;
141705
+ #pending = null;
141706
+ constructor(options) {
141707
+ this.corpId = options.corpId.trim();
141708
+ this.corpSecret = options.corpSecret.trim();
141709
+ this.agentId = options.agentId.trim();
141710
+ this.apiBaseUrl = normalizeApiBaseUrl(options.apiBaseUrl);
141711
+ this.#fetch = options.fetch ?? globalThis.fetch;
141712
+ }
141713
+ invalidateToken() {
141714
+ this.#token = null;
141715
+ this.#expiresAt = 0;
141716
+ }
141717
+ async getAccessToken(signal) {
141718
+ if (this.#token !== null && Date.now() < this.#expiresAt) return this.#token;
141719
+ if (this.#pending !== null) return this.#pending;
141720
+ this.#pending = this.#fetchToken(signal).finally(() => {
141721
+ this.#pending = null;
141722
+ });
141723
+ return this.#pending;
141724
+ }
141725
+ async #fetchToken(signal) {
141726
+ const url = new URL("/cgi-bin/gettoken", this.apiBaseUrl);
141727
+ url.searchParams.set("corpid", this.corpId);
141728
+ url.searchParams.set("corpsecret", this.corpSecret);
141729
+ const payload = await this.#requestJson(url, {
141730
+ method: "GET",
141731
+ ...signal === void 0 ? {} : { signal }
141732
+ });
141733
+ const accessToken = payload["access_token"];
141734
+ if (typeof accessToken !== "string" || accessToken === "") {
141735
+ throw new WecomAppApiError("token-missing", "WeCom did not return access_token.", {
141736
+ providerCode: payload["errcode"] === void 0 ? void 0 : String(payload["errcode"])
141737
+ });
141738
+ }
141739
+ this.#token = accessToken;
141740
+ const lifetime = Number(payload["expires_in"]);
141741
+ const ttlMs = Number.isFinite(lifetime) && lifetime > 60 ? (lifetime - 60) * 1e3 : 7e3 * 1e3;
141742
+ this.#expiresAt = Date.now() + ttlMs;
141743
+ return accessToken;
141744
+ }
141745
+ async sendText(input) {
141746
+ const chunks = splitUtf8ByBytes(input.content);
141747
+ for (const chunk of chunks) {
141748
+ await this.#sendOnce({
141749
+ touser: input.userId,
141750
+ msgtype: "text",
141751
+ agentid: Number.parseInt(this.agentId, 10),
141752
+ text: { content: chunk },
141753
+ signal: input.signal
141754
+ });
141755
+ }
141756
+ }
141757
+ async #sendOnce(body) {
141758
+ const token = await this.getAccessToken(body.signal);
141759
+ const url = new URL("/cgi-bin/message/send", this.apiBaseUrl);
141760
+ url.searchParams.set("access_token", token);
141761
+ const payload = await this.#requestJson(url, {
141762
+ method: "POST",
141763
+ headers: { "content-type": "application/json; charset=utf-8" },
141764
+ body: JSON.stringify({
141765
+ touser: body.touser,
141766
+ msgtype: body.msgtype,
141767
+ agentid: body.agentid,
141768
+ text: body.text
141769
+ }),
141770
+ ...body.signal === void 0 ? {} : { signal: body.signal }
141771
+ });
141772
+ const errcode = Number(payload["errcode"] ?? 0);
141773
+ if (errcode === 0) return;
141774
+ if (errcode === 60020) {
141775
+ throw new WecomAppApiError("trusted-ip", "WeCom rejected the outbound IP (errcode 60020).", {
141776
+ providerCode: "60020",
141777
+ hint: "\u628A\u51FA\u7AD9\u516C\u7F51 IP \u52A0\u8FDB\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u7684\u300C\u4F01\u4E1A\u53EF\u4FE1 IP\u300D\u3002"
141778
+ });
141779
+ }
141780
+ if (errcode === 40014 || errcode === 42001) {
141781
+ this.invalidateToken();
141782
+ }
141783
+ throw new WecomAppApiError(
141784
+ "send-failed",
141785
+ typeof payload["errmsg"] === "string" ? payload["errmsg"] : `WeCom message/send failed (${String(errcode)}).`,
141786
+ { providerCode: String(errcode) }
141787
+ );
141788
+ }
141789
+ async #requestJson(url, init) {
141790
+ const response = await this.#fetch(url, init);
141791
+ let payload;
141792
+ try {
141793
+ payload = await response.json();
141794
+ } catch {
141795
+ throw new WecomAppApiError(
141796
+ "bad-response",
141797
+ `WeCom returned non-JSON (HTTP ${String(response.status)}).`
141798
+ );
141799
+ }
141800
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
141801
+ throw new WecomAppApiError("bad-response", "WeCom returned a non-object JSON body.");
141802
+ }
141803
+ return payload;
141804
+ }
141805
+ };
141806
+
141807
+ // src/wecomAppCallback.ts
141808
+ import * as NodeHttp2 from "node:http";
141809
+
141810
+ // src/wecomAppCrypto.ts
141811
+ import * as NodeCrypto4 from "node:crypto";
141812
+ var AES_KEY_BYTES = 32;
141813
+ var RANDOM_BYTES = 16;
141814
+ var WecomAppCryptoError = class extends Error {
141815
+ code;
141816
+ constructor(code2, message) {
141817
+ super(message);
141818
+ this.name = "WecomAppCryptoError";
141819
+ this.code = code2;
141820
+ }
141821
+ };
141822
+ var isValidEncodingAESKey = (value) => value.length === 43 && /^[A-Za-z0-9]+$/.test(value);
141823
+ var parseEncodingAESKey = (encodingAESKey) => {
141824
+ const key = Buffer.from(`${encodingAESKey}=`, "base64");
141825
+ if (key.length !== AES_KEY_BYTES) {
141826
+ throw new WecomAppCryptoError("invalid-aes-key", "EncodingAESKey must decode to 32 bytes.");
141827
+ }
141828
+ return { key, iv: key.subarray(0, 16) };
141829
+ };
141830
+ var timingSafeEqualString2 = (a, b) => {
141831
+ const left = Buffer.from(a);
141832
+ const right = Buffer.from(b);
141833
+ if (left.length === 0 || left.length !== right.length) return false;
141834
+ return NodeCrypto4.timingSafeEqual(left, right);
141835
+ };
141836
+ var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto4.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
141837
+ var verifySignature = (token, timestamp, nonce, encrypt, signature) => timingSafeEqualString2(computeSignature(token, timestamp, nonce, encrypt), signature);
141838
+ var decrypt = (encodingAESKey, encryptText, expectReceiveId) => {
141839
+ const { key, iv } = parseEncodingAESKey(encodingAESKey);
141840
+ const decipher = NodeCrypto4.createDecipheriv("aes-256-cbc", key, iv);
141841
+ decipher.setAutoPadding(false);
141842
+ let deciphered;
141843
+ try {
141844
+ const unpadded = stripPadding(
141845
+ Buffer.concat([decipher.update(encryptText, "base64"), decipher.final()])
141846
+ );
141847
+ if ("reason" in unpadded) {
141848
+ throw new WecomAppCryptoError("bad-padding", `Invalid padding: ${unpadded.reason}.`);
141849
+ }
141850
+ deciphered = unpadded.bytes;
141851
+ } catch (error) {
141852
+ if (error instanceof WecomAppCryptoError) throw error;
141853
+ throw new WecomAppCryptoError(
141854
+ "decrypt-failed",
141855
+ error instanceof Error ? error.message : String(error)
141856
+ );
141857
+ }
141858
+ if (deciphered.length < RANDOM_BYTES + 4) {
141859
+ throw new WecomAppCryptoError("bad-plaintext", "Deciphered buffer too short.");
141860
+ }
141861
+ const length = deciphered.readUInt32BE(RANDOM_BYTES);
141862
+ const msgStart = RANDOM_BYTES + 4;
141863
+ const msgEnd = msgStart + length;
141864
+ if (msgEnd > deciphered.length) {
141865
+ throw new WecomAppCryptoError("bad-plaintext", "Declared message length exceeds buffer.");
141866
+ }
141867
+ const message = deciphered.subarray(msgStart, msgEnd).toString("utf8");
141868
+ const receiveId = deciphered.subarray(msgEnd).toString("utf8");
141869
+ if (expectReceiveId !== void 0 && expectReceiveId !== "" && !timingSafeEqualString2(receiveId, expectReceiveId)) {
141870
+ throw new WecomAppCryptoError(
141871
+ "receive-id-mismatch",
141872
+ "Decrypted receiveId does not match CorpID."
141873
+ );
141874
+ }
141875
+ return { message, receiveId };
141876
+ };
141877
+ var createWecomAppCrypto = (input) => {
141878
+ const token = input.token.trim();
141879
+ const encodingAESKey = input.encodingAESKey.trim();
141880
+ const corpId = input.corpId.trim();
141881
+ if (!token || !encodingAESKey || !corpId) {
141882
+ throw new WecomAppCryptoError(
141883
+ "missing-credentials",
141884
+ "token, encodingAESKey and corpId are required."
141885
+ );
141886
+ }
141887
+ if (!isValidEncodingAESKey(encodingAESKey)) {
141888
+ throw new WecomAppCryptoError(
141889
+ "invalid-aes-key",
141890
+ "EncodingAESKey must be 43 alphanumeric characters."
141891
+ );
141892
+ }
141893
+ parseEncodingAESKey(encodingAESKey);
141894
+ return {
141895
+ verifySignature: (signature, timestamp, nonce, encryptText) => verifySignature(token, timestamp, nonce, encryptText, signature),
141896
+ decrypt: (encryptText) => decrypt(encodingAESKey, encryptText, corpId).message
141897
+ };
141898
+ };
141899
+ var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto4.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
141900
+
141901
+ // src/wecomAppParse.ts
141902
+ var isRecord2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
141903
+ var isXmlFormat = (raw) => {
141904
+ const trimmed = raw.trim();
141905
+ return trimmed.startsWith("<") && trimmed.endsWith(">");
141906
+ };
141907
+ var parseWecomAppXmlBody = (xml) => {
141908
+ const result = {};
141909
+ const source = typeof xml === "string" ? xml : "";
141910
+ const cdataPattern = /<([\w:-]+)><!\[CDATA\[([\s\S]*?)\]\]><\/\1>/gu;
141911
+ let match;
141912
+ while ((match = cdataPattern.exec(source)) !== null) {
141913
+ result[match[1]] = match[2];
141914
+ }
141915
+ const simplePattern = /<([\w:-]+)>([^<>]*)<\/\1>/gu;
141916
+ while ((match = simplePattern.exec(source)) !== null) {
141917
+ if (result[match[1]] === void 0) result[match[1]] = match[2];
141918
+ }
141919
+ return result;
141920
+ };
141921
+ var parseWecomAppPlainMessage = (raw) => {
141922
+ const source = typeof raw === "string" ? raw.trim() : "";
141923
+ if (!source) return null;
141924
+ if (isXmlFormat(source)) {
141925
+ const xml = parseWecomAppXmlBody(source);
141926
+ return {
141927
+ msgid: xml["MsgId"] ?? xml["msgid"],
141928
+ msgtype: (xml["MsgType"] ?? xml["msgtype"] ?? "").toLowerCase(),
141929
+ createTime: xml["CreateTime"] ?? xml["createTime"],
141930
+ agentId: xml["AgentID"] ?? xml["agentid"],
141931
+ from: xml["FromUserName"] ? { userid: xml["FromUserName"] } : void 0,
141932
+ to: xml["ToUserName"],
141933
+ text: xml["Content"] !== void 0 ? { content: xml["Content"] } : void 0,
141934
+ image: xml["PicUrl"] ? { url: xml["PicUrl"] } : void 0,
141935
+ mediaId: xml["MediaId"] ?? xml["mediaid"],
141936
+ recognition: xml["Recognition"],
141937
+ event: (xml["Event"] ?? xml["event"] ?? "").toLowerCase() || void 0,
141938
+ eventKey: xml["EventKey"],
141939
+ chatid: xml["ChatId"]
141940
+ };
141941
+ }
141942
+ let data;
141943
+ try {
141944
+ data = JSON.parse(source);
141945
+ } catch {
141946
+ return null;
141947
+ }
141948
+ if (!isRecord2(data)) return null;
141949
+ const fromRaw = data["from"];
141950
+ const textRaw = data["text"];
141951
+ const imageRaw = data["image"];
141952
+ return {
141953
+ msgid: typeof data["msgid"] === "string" ? data["msgid"] : typeof data["MsgId"] === "string" ? data["MsgId"] : void 0,
141954
+ msgtype: String(data["msgtype"] ?? data["MsgType"] ?? "").toLowerCase(),
141955
+ createTime: data["createTime"] !== void 0 ? String(data["createTime"]) : data["CreateTime"] !== void 0 ? String(data["CreateTime"]) : void 0,
141956
+ agentId: data["agentid"] !== void 0 ? String(data["agentid"]) : data["AgentID"] !== void 0 ? String(data["AgentID"]) : void 0,
141957
+ from: isRecord2(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
141958
+ to: typeof data["to"] === "string" ? data["to"] : void 0,
141959
+ text: isRecord2(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
141960
+ image: isRecord2(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
141961
+ mediaId: typeof data["mediaid"] === "string" ? data["mediaid"] : void 0,
141962
+ recognition: typeof data["recognition"] === "string" ? data["recognition"] : void 0,
141963
+ event: String(data["event"] ?? data["eventtype"] ?? "").toLowerCase() || void 0,
141964
+ eventKey: typeof data["eventkey"] === "string" ? data["eventkey"] : void 0,
141965
+ chatid: typeof data["chatid"] === "string" ? data["chatid"] : void 0
141966
+ };
141967
+ };
141968
+
141969
+ // src/wecomAppCallback.ts
141970
+ var WECOM_APP_DEFAULT_CALLBACK_HOST = "127.0.0.1";
141971
+ var WECOM_APP_DEFAULT_CALLBACK_PORT = 30987;
141972
+ var WECOM_APP_CALLBACK_PATH_PREFIX = "/wecom-app-callback";
141973
+ var MAX_BODY_BYTES2 = 1 * 1024 * 1024;
141974
+ var writeText = (res, status, text3, headers = {}) => {
141975
+ if (res.writableEnded || res.destroyed) return;
141976
+ const body = text3;
141977
+ res.writeHead(status, {
141978
+ "content-type": "text/plain; charset=utf-8",
141979
+ "content-length": Buffer.byteLength(body),
141980
+ "cache-control": "no-store",
141981
+ ...headers
141982
+ });
141983
+ res.end(body);
141984
+ };
141985
+ var writeEmpty = (res) => {
141986
+ if (res.writableEnded || res.destroyed) return;
141987
+ res.statusCode = 200;
141988
+ res.end();
141989
+ };
141990
+ var callbackRoutePath = (botRowId, token, encodingAESKey) => `${WECOM_APP_CALLBACK_PATH_PREFIX}/${encodeURIComponent(botRowId)}/${deriveCallbackPathSecret(token, encodingAESKey)}`;
141991
+ var startWecomAppCallbackServer = async (options) => {
141992
+ const host = options?.host ?? WECOM_APP_DEFAULT_CALLBACK_HOST;
141993
+ const listenPort = options?.port ?? WECOM_APP_DEFAULT_CALLBACK_PORT;
141994
+ const routes = /* @__PURE__ */ new Map();
141995
+ const cryptoFor = (route) => createWecomAppCrypto({
141996
+ token: route.token,
141997
+ encodingAESKey: route.encodingAESKey,
141998
+ corpId: route.corpId
141999
+ });
142000
+ const handleGet = (route, query, res) => {
142001
+ const timestamp = query.get("timestamp") ?? "";
142002
+ const nonce = query.get("nonce") ?? "";
142003
+ const echostr = query.get("echostr") ?? "";
142004
+ const signature = query.get("msg_signature") ?? query.get("msgsignature") ?? query.get("signature") ?? "";
142005
+ if (!timestamp || !nonce || !echostr || !signature) {
142006
+ writeText(res, 400, "missing query params");
142007
+ return;
142008
+ }
142009
+ let crypto2;
142010
+ try {
142011
+ crypto2 = cryptoFor(route);
142012
+ } catch (error) {
142013
+ route.log?.warn("wecom-app route credentials incomplete", {
142014
+ bot: route.botRowId,
142015
+ error: error instanceof Error ? error.message : String(error)
142016
+ });
142017
+ writeText(res, 503, "route not ready");
142018
+ return;
142019
+ }
142020
+ try {
142021
+ if (!crypto2.verifySignature(signature, timestamp, nonce, echostr)) {
142022
+ writeText(res, 401, "unauthorized");
142023
+ return;
142024
+ }
142025
+ const plaintext = crypto2.decrypt(echostr);
142026
+ route.log?.info("wecom-app URL verification succeeded", { bot: route.botRowId });
142027
+ writeText(res, 200, plaintext);
142028
+ } catch (error) {
142029
+ route.log?.warn("wecom-app URL verification failed", {
142030
+ bot: route.botRowId,
142031
+ error: error instanceof Error ? error.message : String(error)
142032
+ });
142033
+ writeText(res, 400, "verification failed");
142034
+ }
142035
+ };
142036
+ const handlePost = async (route, query, req, res) => {
142037
+ const read = await readBoundedBody(req, MAX_BODY_BYTES2);
142038
+ if (read.kind === "too-large") {
142039
+ writeText(res, 413, "payload too large");
142040
+ return;
142041
+ }
142042
+ if (read.kind === "read-failed") {
142043
+ writeText(res, 400, "failed to read body");
142044
+ return;
142045
+ }
142046
+ const raw = read.bytes.toString("utf8");
142047
+ if (!raw.trim()) {
142048
+ writeText(res, 400, "empty payload");
142049
+ return;
142050
+ }
142051
+ let encrypt = "";
142052
+ let signature = "";
142053
+ let timestamp = "";
142054
+ let nonce = "";
142055
+ if (isXmlFormat(raw)) {
142056
+ const xml = parseWecomAppXmlBody(raw);
142057
+ encrypt = xml["Encrypt"] ?? "";
142058
+ signature = xml["MsgSignature"] ?? query.get("msg_signature") ?? query.get("msgsignature") ?? "";
142059
+ timestamp = xml["TimeStamp"] ?? query.get("timestamp") ?? "";
142060
+ nonce = xml["Nonce"] ?? query.get("nonce") ?? "";
142061
+ } else {
142062
+ let parsed;
142063
+ try {
142064
+ parsed = JSON.parse(raw);
142065
+ } catch {
142066
+ writeText(res, 400, "invalid payload");
142067
+ return;
142068
+ }
142069
+ encrypt = typeof parsed["encrypt"] === "string" ? parsed["encrypt"] : "";
142070
+ signature = typeof parsed["msgsignature"] === "string" ? parsed["msgsignature"] : typeof parsed["MsgSignature"] === "string" ? parsed["MsgSignature"] : query.get("msg_signature") ?? "";
142071
+ timestamp = String(
142072
+ parsed["timestamp"] ?? parsed["TimeStamp"] ?? query.get("timestamp") ?? ""
142073
+ );
142074
+ nonce = String(parsed["nonce"] ?? parsed["Nonce"] ?? query.get("nonce") ?? "");
142075
+ }
142076
+ if (!encrypt || !signature || !timestamp || !nonce) {
142077
+ writeText(res, 400, "missing encrypted payload");
142078
+ return;
142079
+ }
142080
+ let crypto2;
142081
+ try {
142082
+ crypto2 = cryptoFor(route);
142083
+ } catch {
142084
+ writeText(res, 503, "route not ready");
142085
+ return;
142086
+ }
142087
+ let plaintext;
142088
+ try {
142089
+ if (!crypto2.verifySignature(signature, timestamp, nonce, encrypt)) {
142090
+ writeText(res, 401, "unauthorized");
142091
+ return;
142092
+ }
142093
+ plaintext = crypto2.decrypt(encrypt);
142094
+ } catch (error) {
142095
+ route.log?.warn("wecom-app callback signature or decrypt failed", {
142096
+ bot: route.botRowId,
142097
+ error: error instanceof Error ? error.message : String(error)
142098
+ });
142099
+ writeText(res, 401, "unauthorized");
142100
+ return;
142101
+ }
142102
+ const message = parseWecomAppPlainMessage(plaintext);
142103
+ if (message === null) {
142104
+ writeText(res, 400, "invalid message");
142105
+ return;
142106
+ }
142107
+ writeEmpty(res);
142108
+ try {
142109
+ await route.onInbound(message);
142110
+ } catch (error) {
142111
+ route.log?.warn("wecom-app inbound handler failed", {
142112
+ bot: route.botRowId,
142113
+ error: error instanceof Error ? error.message : String(error)
142114
+ });
142115
+ }
142116
+ };
142117
+ const server = NodeHttp2.createServer((req, res) => {
142118
+ void (async () => {
142119
+ if (req.method !== "GET" && req.method !== "POST") {
142120
+ writeText(res, 405, "method not allowed", { allow: "GET, POST" });
142121
+ return;
142122
+ }
142123
+ const url = new URL(req.url ?? "/", `http://${host}`);
142124
+ const route = routes.get(url.pathname);
142125
+ if (route === void 0) {
142126
+ writeText(res, 404, "not found");
142127
+ return;
142128
+ }
142129
+ if (req.method === "GET") {
142130
+ handleGet(route, url.searchParams, res);
142131
+ return;
142132
+ }
142133
+ await handlePost(route, url.searchParams, req, res);
142134
+ })().catch((error) => {
142135
+ if (!res.headersSent) writeText(res, 500, "internal error");
142136
+ void error;
142137
+ });
142138
+ });
142139
+ await new Promise((resolve, reject) => {
142140
+ server.once("error", reject);
142141
+ server.listen(listenPort, host, () => resolve());
142142
+ });
142143
+ const address = server.address();
142144
+ const port = address.port;
142145
+ return {
142146
+ host,
142147
+ port,
142148
+ origin: `http://${host}:${String(port)}`,
142149
+ register: (route) => {
142150
+ const path = callbackRoutePath(route.botRowId, route.token, route.encodingAESKey);
142151
+ routes.set(path, { ...route, path });
142152
+ route.log?.info("wecom-app callback route registered", {
142153
+ bot: route.botRowId,
142154
+ path,
142155
+ origin: `http://${host}:${String(port)}`
142156
+ });
142157
+ return path;
142158
+ },
142159
+ unregister: (botRowId) => {
142160
+ for (const [path, route] of routes.entries()) {
142161
+ if (route.botRowId === botRowId) routes.delete(path);
142162
+ }
142163
+ },
142164
+ close: () => new Promise((resolve, reject) => {
142165
+ routes.clear();
142166
+ server.close((error) => error ? reject(error) : resolve());
142167
+ })
142168
+ };
142169
+ };
142170
+ var makeWecomAppCallbackHub = () => {
142171
+ const open = /* @__PURE__ */ new Map();
142172
+ const closing = /* @__PURE__ */ new Map();
142173
+ const acquire = async (options) => {
142174
+ const host = options?.host ?? WECOM_APP_DEFAULT_CALLBACK_HOST;
142175
+ const port = options?.port ?? WECOM_APP_DEFAULT_CALLBACK_PORT;
142176
+ const key = `${host}:${String(port)}`;
142177
+ let entry = open.get(key);
142178
+ if (entry === void 0) {
142179
+ const created = {
142180
+ server: (closing.get(key) ?? Promise.resolve()).then(
142181
+ () => startWecomAppCallbackServer({ host, port })
142182
+ ),
142183
+ users: 0
142184
+ };
142185
+ entry = created;
142186
+ open.set(key, created);
142187
+ created.server.catch(() => {
142188
+ if (open.get(key) === created) open.delete(key);
142189
+ });
142190
+ }
142191
+ const owner = entry;
142192
+ owner.users += 1;
142193
+ let server;
142194
+ try {
142195
+ server = await owner.server;
142196
+ } catch (error) {
142197
+ owner.users -= 1;
142198
+ throw error;
142199
+ }
142200
+ let released = false;
142201
+ return {
142202
+ server,
142203
+ release: async () => {
142204
+ if (released) return;
142205
+ released = true;
142206
+ owner.users -= 1;
142207
+ if (owner.users > 0) return;
142208
+ if (open.get(key) === owner) open.delete(key);
142209
+ const done = server.close().catch(() => void 0);
142210
+ closing.set(key, done);
142211
+ await done;
142212
+ if (closing.get(key) === done) closing.delete(key);
142213
+ }
142214
+ };
142215
+ };
142216
+ return { acquire };
142217
+ };
142218
+
142219
+ // src/wecomAppAdapter.ts
142220
+ var COLUMNS5 = [
142221
+ {
142222
+ key: "wecomAppCorpId",
142223
+ kind: "text",
142224
+ labelKey: "column.wecomAppCorpId.label",
142225
+ placeholderKey: "column.wecomAppCorpId.placeholder"
142226
+ },
142227
+ {
142228
+ key: "wecomAppAgentId",
142229
+ kind: "text",
142230
+ labelKey: "column.wecomAppAgentId.label",
142231
+ placeholderKey: "column.wecomAppAgentId.placeholder"
142232
+ },
142233
+ { key: "wecomAppSecret", kind: "password", labelKey: "column.wecomAppSecret.label" },
142234
+ { key: "wecomAppToken", kind: "password", labelKey: "column.wecomAppToken.label" },
142235
+ {
142236
+ key: "wecomAppEncodingAESKey",
142237
+ kind: "password",
142238
+ labelKey: "column.wecomAppEncodingAESKey.label",
142239
+ placeholderKey: "column.wecomAppEncodingAESKey.placeholder"
142240
+ },
142241
+ {
142242
+ key: "wecomAppCallbackBaseUrl",
142243
+ kind: "text",
142244
+ labelKey: "column.wecomAppCallbackBaseUrl.label",
142245
+ descriptionKey: "column.wecomAppCallbackBaseUrl.description",
142246
+ placeholderKey: "column.wecomAppCallbackBaseUrl.placeholder"
142247
+ },
142248
+ {
142249
+ key: "wecomAppApiBaseUrl",
142250
+ kind: "text",
142251
+ labelKey: "column.wecomAppApiBaseUrl.label",
142252
+ descriptionKey: "column.wecomAppApiBaseUrl.description",
142253
+ placeholderKey: "column.wecomAppApiBaseUrl.placeholder"
142254
+ },
142255
+ {
142256
+ key: "wecomAppListenPort",
142257
+ kind: "text",
142258
+ labelKey: "column.wecomAppListenPort.label",
142259
+ descriptionKey: "column.wecomAppListenPort.description",
142260
+ placeholderKey: "column.wecomAppListenPort.placeholder"
142261
+ }
142262
+ ];
142263
+ var wecomAppCredentials = (bot) => ({
142264
+ corpId: bot.values["wecomAppCorpId"] ?? "",
142265
+ agentId: bot.values["wecomAppAgentId"] ?? "",
142266
+ corpSecret: bot.values["wecomAppSecret"] ?? "",
142267
+ token: bot.values["wecomAppToken"] ?? "",
142268
+ encodingAESKey: bot.values["wecomAppEncodingAESKey"] ?? "",
142269
+ callbackBaseUrl: (bot.values["wecomAppCallbackBaseUrl"] ?? "").trim().replace(/\/+$/u, ""),
142270
+ apiBaseUrl: (bot.values["wecomAppApiBaseUrl"] ?? "").trim(),
142271
+ listenPortRaw: (bot.values["wecomAppListenPort"] ?? "").trim()
142272
+ });
142273
+ var parseListenPort = (raw) => {
142274
+ if (raw === "") return WECOM_APP_DEFAULT_CALLBACK_PORT;
142275
+ const n = Number.parseInt(raw, 10);
142276
+ if (!Number.isInteger(n) || n < 0 || n > 65535) return WECOM_APP_DEFAULT_CALLBACK_PORT;
142277
+ return n;
142278
+ };
142279
+ var makeWecomAppAdapter = (deps) => {
142280
+ const apis = /* @__PURE__ */ new Map();
142281
+ const newApi = (c) => new WecomAppApi({
142282
+ corpId: c.corpId,
142283
+ corpSecret: c.corpSecret,
142284
+ agentId: c.agentId,
142285
+ apiBaseUrl: c.apiBaseUrl || void 0,
142286
+ fetch: deps.fetch
142287
+ });
142288
+ const callbackHub = makeWecomAppCallbackHub();
142289
+ const toInbound = (message, handlers) => {
142290
+ if (message.msgtype === "event") return;
142291
+ const senderId = message.from?.userid ?? null;
142292
+ const chatId = message.chatid ?? message.from?.userid ?? "";
142293
+ if (chatId === "") return;
142294
+ if (message.msgtype === "text") {
142295
+ handlers.onMessage({
142296
+ messageId: message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`,
142297
+ chatId,
142298
+ senderId,
142299
+ text: message.text?.content?.trim() ?? "",
142300
+ resources: []
142301
+ });
142302
+ return;
142303
+ }
142304
+ if (message.msgtype === "image") {
142305
+ handlers.onMessage({
142306
+ messageId: message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`,
142307
+ chatId,
142308
+ senderId,
142309
+ text: "",
142310
+ resources: message.mediaId ? [
142311
+ {
142312
+ key: message.mediaId,
142313
+ name: "image.jpg",
142314
+ mimeType: "image/jpeg",
142315
+ kind: "image"
142316
+ }
142317
+ ] : [],
142318
+ ...message.mediaId ? {} : { unsupportedType: "image" }
142319
+ });
142320
+ return;
142321
+ }
142322
+ handlers.onMessage({
142323
+ messageId: message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`,
142324
+ chatId,
142325
+ senderId,
142326
+ text: "",
142327
+ resources: [],
142328
+ unsupportedType: message.msgtype || "unknown"
142329
+ });
142330
+ };
142331
+ return {
142332
+ platform: "wecom-app",
142333
+ icon: "wecom",
142334
+ label: deps.translate("platform.wecom-app"),
142335
+ columns: COLUMNS5,
142336
+ isConfigured: (bot) => {
142337
+ const c = wecomAppCredentials(bot);
142338
+ return c.corpId !== "" && c.agentId !== "" && c.corpSecret !== "" && c.token !== "" && isValidEncodingAESKey(c.encodingAESKey);
142339
+ },
142340
+ /**
142341
+ * 自建应用是回调式渠道:入站由企微推到我们的 listener,没有可探的长连接;能探的是出站
142342
+ * 凭据——和真实发送同一条路,向 gettoken 换一次 access_token。
142343
+ */
142344
+ checkConnection: async (bot) => {
142345
+ const c = wecomAppCredentials(bot);
142346
+ if (c.corpId === "" || c.agentId === "" || c.corpSecret === "") {
142347
+ return {
142348
+ stage: "credentials",
142349
+ ok: false,
142350
+ detail: "missing corp id, agent id or secret in saved config"
142351
+ };
142352
+ }
142353
+ try {
142354
+ await newApi(c).getAccessToken();
142355
+ return {
142356
+ stage: "gettoken",
142357
+ ok: true,
142358
+ detail: "access_token ok (callback reachability is not probed)"
142359
+ };
142360
+ } catch (error) {
142361
+ return { stage: "gettoken", ok: false, detail: describeError(error) };
142362
+ }
142363
+ },
142364
+ connect: async (bot, handlers) => {
142365
+ const c = wecomAppCredentials(bot);
142366
+ const port = parseListenPort(c.listenPortRaw);
142367
+ const lease = await callbackHub.acquire({ port });
142368
+ const server = lease.server;
142369
+ const api = newApi(c);
142370
+ apis.set(bot.rowId, api);
142371
+ let path;
142372
+ try {
142373
+ path = server.register({
142374
+ botRowId: bot.rowId,
142375
+ token: c.token,
142376
+ encodingAESKey: c.encodingAESKey,
142377
+ corpId: c.corpId,
142378
+ log: deps.log,
142379
+ onInbound: (message) => toInbound(message, handlers)
142380
+ });
142381
+ } catch (error) {
142382
+ await lease.release();
142383
+ throw error;
142384
+ }
142385
+ const fullUrl = c.callbackBaseUrl !== "" ? `${c.callbackBaseUrl}${path}` : `${server.origin}${path}`;
142386
+ deps.log.info("wecom-app connected", {
142387
+ bot: bot.displayName,
142388
+ callbackPath: path,
142389
+ callbackUrl: fullUrl,
142390
+ listen: `${server.host}:${String(server.port)}`
142391
+ });
142392
+ return {
142393
+ close: () => {
142394
+ server.unregister(bot.rowId);
142395
+ apis.delete(bot.rowId);
142396
+ void lease.release().catch((error) => {
142397
+ deps.log.warn("wecom-app callback listener did not close cleanly", {
142398
+ bot: bot.displayName,
142399
+ error: error instanceof Error ? error.message : String(error)
142400
+ });
142401
+ });
142402
+ }
142403
+ };
142404
+ },
142405
+ sendText: async (binding, text3, options) => {
142406
+ if (options?.targetKind === "group") {
142407
+ throw new Error("wecom-app can only deliver to a user; group targets are not supported.");
142408
+ }
142409
+ const api = apis.get(binding.rowId);
142410
+ if (api === void 0) {
142411
+ throw new ImNotConnectedError(`wecom-app bot '${binding.displayName}' is not connected.`);
142412
+ }
142413
+ await api.sendText({ userId: binding.chatId, content: text3 });
142414
+ },
142415
+ askApproval: async (binding, request) => {
142416
+ const api = apis.get(binding.rowId);
142417
+ if (api === void 0) return null;
142418
+ await api.sendText({ userId: binding.chatId, content: numberedApprovalText(request) });
142419
+ return null;
142420
+ },
142421
+ answerByText: (_binding, text3, options) => answerByNumber(text3, options)
142422
+ };
142423
+ };
142424
+
140295
142425
  // src/index.ts
140296
142426
  var imPlugin = definePlugin({
140297
142427
  id: "im",
@@ -140331,10 +142461,28 @@ var imPlugin = definePlugin({
140331
142461
  info: (message, fields) => ctx.log.info(message, fields),
140332
142462
  warn: (message, fields) => ctx.log.warn(message, fields)
140333
142463
  }
142464
+ }),
142465
+ makeWecomAppAdapter({
142466
+ // 自建应用:HTTP 回调收消息 + cgi-bin 回文字(YCO-235)。
142467
+ fetch: ctx.http,
142468
+ translate: t,
142469
+ log: {
142470
+ info: (message, fields) => ctx.log.info(message, fields),
142471
+ warn: (message, fields) => ctx.log.warn(message, fields)
142472
+ }
142473
+ }),
142474
+ // 微信个微:配置面先挂上;真 iLink 登录等 0069 安全评审合入门槛通过后再实装。
142475
+ makeWechatAdapter({
142476
+ translate: t,
142477
+ log: {
142478
+ info: (message, fields) => ctx.log.info(message, fields),
142479
+ warn: (message, fields) => ctx.log.warn(message, fields)
142480
+ }
140334
142481
  })
140335
142482
  ];
140336
142483
  ctx.settingsCard({ ...buildSettingsCard(adapters), actions: [LARK_SETUP_CARD_ACTION] });
140337
142484
  registerLarkSetup(ctx);
142485
+ registerSettingsRowActions(ctx, adapters);
140338
142486
  const core = makeImCore({
140339
142487
  host: {
140340
142488
  log: ctx.log,
@@ -140360,13 +142508,15 @@ var imPlugin = definePlugin({
140360
142508
  clearWorking: acknowledgements.clearWorking,
140361
142509
  replyCards
140362
142510
  });
142511
+ const webSync = makeWebSync(core);
140363
142512
  const approvals = makeApprovals(core);
140364
142513
  const interactions = makeInteractions(core, approvals);
140365
142514
  const inboundAttachments = makeInboundAttachments(core);
140366
142515
  const turns = makeTurns(core, {
140367
142516
  attachments: inboundAttachments,
140368
142517
  queueFlow: interactions.queueFlow,
140369
- rememberThreadProject: notifications.rememberThreadProject
142518
+ rememberThreadProject: notifications.rememberThreadProject,
142519
+ noteInboundUserMessage: webSync.noteInboundUserMessage
140370
142520
  });
140371
142521
  const sessions = makeSessions(core);
140372
142522
  const modelPlan = makeModelPlan(core, turns);
@@ -140419,6 +142569,34 @@ var imPlugin = definePlugin({
140419
142569
  ]
140420
142570
  });
140421
142571
  const { bots, connecting } = startConnections(core, inbound.inboundHandlersFor);
142572
+ if (readDeliveryHttpEnabled(ctx.config)) {
142573
+ const port = readDeliveryHttpPort(ctx.config);
142574
+ const token = readDeliveryHttpToken(ctx.config);
142575
+ const send = makeDeliverySender(core, ctx.config);
142576
+ try {
142577
+ const server = await startDeliveryHttpServer({
142578
+ port,
142579
+ token,
142580
+ send,
142581
+ log: {
142582
+ info: (message, fields) => ctx.log.info(message, fields),
142583
+ warn: (message, fields) => ctx.log.warn(message, fields)
142584
+ }
142585
+ });
142586
+ ctx.onDispose(() => {
142587
+ void server.close().catch((error) => {
142588
+ ctx.log.warn("delivery http could not close", {
142589
+ error: error instanceof Error ? error.message : String(error)
142590
+ });
142591
+ });
142592
+ });
142593
+ } catch (error) {
142594
+ ctx.log.warn("delivery http failed to listen", {
142595
+ port,
142596
+ error: error instanceof Error ? error.message : String(error)
142597
+ });
142598
+ }
142599
+ }
140422
142600
  ctx.log.info("chat plugin active", { bots, connecting });
140423
142601
  }
140424
142602
  });