vibe-coding-master 0.4.33 → 0.4.35

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/README.md CHANGED
@@ -221,7 +221,7 @@ The left sidebar is intentionally compact and collapsible:
221
221
  - `Settings`: `Theme`, `Flow pause alert`, `Try alert`, `Messages`, and `Events`.
222
222
  - `Translation`: global conversation translation, auto-send, target language, output scope, file translation, bootstrap, memory update, session status, and Translator session access.
223
223
  - `Gate Review Gates`: global gate switches for architecture plan, validation adequacy, and final diff.
224
- - `Gateway`: Weixin iLink or Lark binding, Gateway on/off, Gateway translation, QR login/setup, and Lark pairing.
224
+ - `Gateway`: Weixin iLink or Lark setup, Gateway on/off, Gateway translation, and QR login/setup.
225
225
  - `VCM Harness`: fixed-install status, bootstrap completion checks, and the bootstrap terminal when one is running.
226
226
  - `New Task`: one `task name` input.
227
227
  - `Tasks`: task list and task status.
@@ -238,15 +238,16 @@ When VCM is connected to an active task, the bottom of the sidebar shows a task
238
238
 
239
239
  ## Mobile Gateway
240
240
 
241
- VCM Gateway lets one mobile chat identity bind to one desktop VCM instance. It supports Weixin iLink and Lark. It is a mobile control surface for the current desktop VCM, not a remote terminal and not a multi-user collaboration surface.
241
+ VCM Gateway lets mobile chat clients talk to one desktop VCM instance. It supports Weixin iLink and Lark. It is a mobile control surface for the current desktop VCM, not a remote terminal.
242
242
 
243
243
  Gateway rules:
244
244
 
245
245
  - Weixin is DM only; Lark can receive group messages only when the bot is mentioned.
246
- - One phone identity binds to one desktop VCM instance.
247
- - The phone can manage projects and tasks available to that desktop VCM instance.
246
+ - Weixin binds one phone identity to one desktop VCM instance.
247
+ - Lark accepts any DM or @mention from a chat that can reach the bot; the most recent active Lark chat becomes the PM reply target.
248
+ - The active mobile chat can manage projects and tasks available to that desktop VCM instance.
248
249
  - When the desktop UI has an active task selected, Gateway uses that task automatically.
249
- - After binding, VCM keeps a lightweight Gateway connection for `/start` and read-only commands even when the `Gateway` toggle is off.
250
+ - After setup, VCM keeps a lightweight Gateway connection for `/start` and read-only commands even when the `Gateway` toggle is off.
250
251
  - VCM caches the latest PM reply for each task locally, so `/start` can immediately return the current task's latest PM status when available.
251
252
  - Plain text messages go only to the current task's `project-manager`.
252
253
  - Gateway never sends directly to `architect`, `coder`, or `reviewer`.
@@ -279,11 +280,10 @@ The `Gateway` toggle is disabled until a QR login has produced a usable iLink to
279
280
  2. Click `Start QR Setup`.
280
281
  3. Scan the QR code with Lark and approve bot creation.
281
282
  4. Click `Confirm` in the setup dialog.
282
- 5. Click `Create Pairing Code`.
283
- 6. Send `/bind CODE` to the Lark bot before the code expires.
284
- 7. After binding succeeds, turn `Gateway` on in the sidebar.
283
+ 5. Turn `Gateway` on in the sidebar.
284
+ 6. Send a DM to the bot, or @mention it from a group.
285
285
 
286
- Lark QR setup creates/configures the bot app and stores the resulting App ID/App Secret in local VCM state. The App Secret is not shown in the UI. `Reset Binding` clears the bound Lark user/chat state while keeping the saved Lark app credentials.
286
+ Lark QR setup creates/configures the bot app and stores the resulting App ID/App Secret in local VCM state. The App Secret is not shown in the UI. Any Lark chat that can message the bot can control Gateway; VCM always treats the most recent active Lark chat as the PM reply target.
287
287
 
288
288
  When Gateway is turned on, VCM automatically turns off the browser `Flow pause alert` and disables `Try alert`. Gateway becomes the notification path, so the browser should not show blocking flow-pause dialogs while the user is managing the task from the phone.
289
289
 
@@ -296,14 +296,14 @@ When Gateway translation is on:
296
296
  - Mobile input is translated to English before being submitted to PM.
297
297
  - The prompt sent to PM includes only the translated English text with a `[VCM Gateway]` marker.
298
298
  - The original Chinese text is not included in the PM prompt.
299
- - PM replies are translated before VCM sends them to the bound mobile chat.
299
+ - PM replies are translated before VCM sends them to the active mobile chat.
300
300
  - If PM reply translation fails or times out, VCM sends a translation failure notice instead of the English source. The user can send `/retry` to retry the latest failed Gateway output translation.
301
301
 
302
302
  When Gateway translation is off, plain mobile text is sent to PM as-is.
303
303
 
304
304
  ### Commands
305
305
 
306
- After Gateway is bound, send commands in the bound mobile conversation.
306
+ After Gateway is configured, send commands in the active mobile conversation.
307
307
 
308
308
  When `Gateway` is off, only these commands are accepted:
309
309
 
@@ -353,9 +353,9 @@ Typical mobile flow:
353
353
 
354
354
  ### Command Behavior
355
355
 
356
- - `/status`: shows Gateway, binding, translation, current project, current task, and last poll status.
356
+ - `/status`: shows Gateway, active chat, translation, current project, current task, and last poll status.
357
357
  - `/status` also adopts the current desktop project/task when one is selected.
358
- - `/start`: turns Gateway on from the bound mobile conversation so full mobile task operations and PM messages are allowed. If the current task has a cached latest PM reply, `/start` includes it in the response.
358
+ - `/start`: turns Gateway on from the active mobile conversation so full mobile task operations and PM messages are allowed. If the current task has a cached latest PM reply, `/start` includes it in the response.
359
359
  - `/retry`: retries the latest failed Gateway output translation in the current VCM process.
360
360
  - `/projects`: lists the current/recent repositories known by the desktop VCM.
361
361
  - `/use-project <index-or-path>`: selects the Gateway's current project context.
@@ -378,12 +378,12 @@ Typical mobile flow:
378
378
  - If the QR dialog does not appear, refresh the page and click `Start QR Login` again.
379
379
  - If the QR status stays `wait`, confirm the login on the phone and click `Confirm` again.
380
380
  - If the QR code expires, start a new QR login.
381
- - If `Gateway` cannot be enabled, bind Weixin or complete Lark QR setup and pairing first.
382
- - If `/start` or read-only commands do not receive replies, check that the selected channel is connected and the message comes from the bound identity.
381
+ - If `Gateway` cannot be enabled, bind Weixin or complete Lark QR setup first.
382
+ - If `/start` or read-only commands do not receive replies, check that the selected channel is connected and that Lark messages are sent as DM or group @mentions.
383
383
  - If PM messages or task-changing commands are rejected, check that Gateway is on.
384
384
  - If plain text cannot be sent to PM, select a project and task first, and make sure the task's PM session is running and idle.
385
385
  - If PM replies are not pushed, check that Gateway is on and the PM session is producing normal Claude transcript output.
386
- - If PM reply translation fails, send `/retry` from the bound mobile conversation. Retry state is memory-only and is cleared when VCM restarts.
386
+ - If PM reply translation fails, send `/retry` from the active mobile conversation. Retry state is memory-only and is cleared when VCM restarts.
387
387
 
388
388
  ## Translation
389
389
 
@@ -8,9 +8,6 @@ export function registerGatewayRoutes(app, deps) {
8
8
  app.post("/api/gateway/qr/start", async () => {
9
9
  return deps.gatewayService.startQrLogin();
10
10
  });
11
- app.post("/api/gateway/pairing-code", async () => {
12
- return deps.gatewayService.createPairingCode();
13
- });
14
11
  app.post("/api/gateway/qr/check", async (request) => {
15
12
  return deps.gatewayService.checkQrLogin(request.body);
16
13
  });
@@ -24,8 +24,9 @@ export function createLarkRegistrationClient(deps = {}) {
24
24
  return {
25
25
  async init(domain) {
26
26
  const payload = await postRegistration(domain, { action: "init" });
27
- const methods = Array.isArray(payload.supported_auth_methods)
28
- ? payload.supported_auth_methods.filter((value) => typeof value === "string")
27
+ const data = responseData(payload);
28
+ const methods = Array.isArray(data.supported_auth_methods)
29
+ ? data.supported_auth_methods.filter((value) => typeof value === "string")
29
30
  : [];
30
31
  if (!methods.includes("client_secret")) {
31
32
  throw new Error(`Lark QR setup does not support client_secret auth. Supported methods: ${methods.join(", ") || "none"}.`);
@@ -38,11 +39,12 @@ export function createLarkRegistrationClient(deps = {}) {
38
39
  auth_method: "client_secret",
39
40
  request_user_info: "open_id"
40
41
  });
41
- const deviceCode = stringOrNull(payload.device_code);
42
+ const data = responseData(payload);
43
+ const deviceCode = stringOrNull(data.device_code);
42
44
  if (!deviceCode) {
43
45
  throw new Error("Lark QR setup did not return a device_code.");
44
46
  }
45
- const qrUrl = appendQrTrackingParams(stringOrNull(payload.verification_uri_complete) ?? "");
47
+ const qrUrl = appendQrTrackingParams(stringOrNull(data.verification_uri_complete) ?? "");
46
48
  if (!qrUrl) {
47
49
  throw new Error("Lark QR setup did not return a QR URL.");
48
50
  }
@@ -50,9 +52,9 @@ export function createLarkRegistrationClient(deps = {}) {
50
52
  domain,
51
53
  deviceCode,
52
54
  qrUrl,
53
- userCode: stringOrNull(payload.user_code),
54
- intervalSeconds: positiveNumberOr(payload.interval, 5),
55
- expiresInSeconds: positiveNumberOr(payload.expire_in, 600)
55
+ userCode: stringOrNull(data.user_code),
56
+ intervalSeconds: positiveNumberOr(data.interval, 5),
57
+ expiresInSeconds: positiveNumberOr(data.expire_in, 600)
56
58
  };
57
59
  },
58
60
  async poll(input) {
@@ -61,11 +63,12 @@ export function createLarkRegistrationClient(deps = {}) {
61
63
  device_code: input.deviceCode,
62
64
  tp: "ob_app"
63
65
  });
64
- const userInfo = isObject(payload.user_info) ? payload.user_info : {};
66
+ const data = responseData(payload);
67
+ const userInfo = isObject(data.user_info) ? data.user_info : {};
65
68
  const tenantBrand = stringOrNull(userInfo.tenant_brand);
66
69
  const domain = tenantBrand === "lark" ? "lark" : input.domain;
67
- const appId = stringOrNull(payload.client_id);
68
- const appSecret = stringOrNull(payload.client_secret);
70
+ const appId = stringOrNull(data.client_id);
71
+ const appSecret = stringOrNull(data.client_secret);
69
72
  if (appId && appSecret) {
70
73
  const bot = await probeBot(fetchImpl, {
71
74
  appId,
@@ -82,20 +85,31 @@ export function createLarkRegistrationClient(deps = {}) {
82
85
  botOpenId: bot.botOpenId
83
86
  };
84
87
  }
85
- const error = stringOrNull(payload.error);
88
+ const error = stringOrNull(data.error) ?? stringOrNull(payload.error);
86
89
  if (error === "expired_token") {
87
90
  return { status: "expired", message: "Lark QR setup expired. Start a new setup." };
88
91
  }
89
92
  if (error === "access_denied") {
90
93
  return { status: "failed", message: "Lark QR setup was denied." };
91
94
  }
95
+ const errorDescription = stringOrNull(data.error_description)
96
+ ?? stringOrNull(payload.error_description)
97
+ ?? stringOrNull(data.message)
98
+ ?? stringOrNull(payload.message)
99
+ ?? stringOrNull(data.msg)
100
+ ?? stringOrNull(payload.msg);
92
101
  return {
93
102
  status: "wait",
94
- message: error && error !== "authorization_pending" ? error : undefined
103
+ message: error && error !== "authorization_pending"
104
+ ? [error, errorDescription].filter(Boolean).join(": ")
105
+ : undefined
95
106
  };
96
107
  }
97
108
  };
98
109
  }
110
+ function responseData(payload) {
111
+ return isObject(payload.data) ? payload.data : payload;
112
+ }
99
113
  async function probeBot(fetchImpl, input) {
100
114
  try {
101
115
  const tokenPayload = await fetchJson(fetchImpl, `${OPEN_BASE_URLS[input.domain]}/open-apis/auth/v3/tenant_access_token/internal`, {
@@ -155,7 +169,7 @@ async function fetchJson(fetchImpl, url, init = {}) {
155
169
  throw new Error("Lark setup response was not a JSON object.");
156
170
  }
157
171
  if (!response.ok && !payload.error) {
158
- throw new Error(`${response.status} ${response.statusText}`);
172
+ throw new Error(`Lark QR setup request failed: ${response.status} ${response.statusText}${formatPayloadError(payload)}`);
159
173
  }
160
174
  return payload;
161
175
  }
@@ -169,6 +183,18 @@ async function fetchJson(fetchImpl, url, init = {}) {
169
183
  clearTimeout(timeout);
170
184
  }
171
185
  }
186
+ function formatPayloadError(payload) {
187
+ const data = responseData(payload);
188
+ const message = [
189
+ stringOrNull(data.error) ?? stringOrNull(payload.error),
190
+ stringOrNull(data.error_description) ?? stringOrNull(payload.error_description),
191
+ stringOrNull(data.message) ?? stringOrNull(payload.message),
192
+ stringOrNull(data.msg) ?? stringOrNull(payload.msg),
193
+ typeof data.code === "number" || typeof data.code === "string" ? `code ${data.code}` : null,
194
+ typeof payload.code === "number" || typeof payload.code === "string" ? `code ${payload.code}` : null
195
+ ].filter(Boolean).join(": ");
196
+ return message ? ` (${message})` : "";
197
+ }
172
198
  function appendQrTrackingParams(value) {
173
199
  if (!value) {
174
200
  return "";
@@ -1,4 +1,3 @@
1
- import { randomInt } from "node:crypto";
2
1
  import { readFile } from "node:fs/promises";
3
2
  import { CORE_VCM_ROLE_DEFINITIONS, GATE_REVIEWER_ROLE_DEFINITION, VCM_ROLE_NAMES } from "../../shared/constants.js";
4
3
  import { VcmError } from "../errors.js";
@@ -8,12 +7,12 @@ import { parseAssistantContent, resolveExistingClaudeTranscriptPath } from "../s
8
7
  import { parseGatewayCommand } from "./gateway-command-parser.js";
9
8
  import { createLarkRegistrationClient } from "./channels/lark-registration.js";
10
9
  const QR_LOGIN_TTL_MS = 8 * 60 * 1000;
11
- const PAIRING_CODE_TTL_MS = 10 * 60 * 1000;
12
10
  const CLOSE_CONFIRM_TTL_MS = 10 * 60 * 1000;
13
11
  const POLL_ERROR_BACKOFF_MS = 2_000;
14
12
  const POLL_LONG_BACKOFF_MS = 30_000;
15
13
  const MAX_FAILURES_BEFORE_LONG_BACKOFF = 3;
16
14
  const DEFAULT_POLL_TIMEOUT_MS = 35_000;
15
+ const LARK_REGISTRATION_CONFIRM_TIMEOUT_MS = 15_000;
17
16
  const MAX_LATEST_PM_REPLY_CHARS = 8_000;
18
17
  const GATEWAY_TRANSLATION_FAILURE_TEXT = "PM 回复已收到,但翻译失败。\n发送 /retry 重新翻译。";
19
18
  const COMMANDS_ALLOWED_WHEN_DISABLED = new Set([
@@ -202,37 +201,10 @@ export function createGatewayService(deps) {
202
201
  });
203
202
  return;
204
203
  }
205
- if (settings.channel === "lark" && !settings.binding.boundUserId) {
206
- settings = await saveInboundMetadata(settings, update);
207
- const pairing = parseLarkPairingCommand(update.text);
208
- if (!pairing || !isValidPairingCode(settings, pairing)) {
209
- await reply(settings, update.fromUserId, "Lark Gateway is not paired. Generate a pairing code in desktop VCM, then send /bind CODE here.");
210
- await recordMessageStatus("inbound", "ignored", update.text, "lark gateway not paired");
211
- return;
212
- }
213
- await deps.settings.saveSettings({
214
- ...settings,
215
- binding: {
216
- ...settings.binding,
217
- boundUserId: update.fromUserId,
218
- loginUserId: update.fromUserId,
219
- pairingCode: null,
220
- pairingCodeExpiresAt: null,
221
- chatIds: update.chatId
222
- ? {
223
- ...settings.binding.chatIds,
224
- [update.fromUserId]: update.chatId
225
- }
226
- : settings.binding.chatIds
227
- },
228
- updatedAt: now()
229
- });
230
- await reply(await deps.settings.loadSettings(), update.fromUserId, "Lark Gateway bound. Send /help for available commands.");
231
- await recordMessageStatus("inbound", "ok", update.text, undefined, "bind");
232
- return;
233
- }
234
- settings = await saveInboundMetadata(settings, update, { bindIfMissing: true });
235
- if (settings.binding.boundUserId !== update.fromUserId) {
204
+ settings = await saveInboundMetadata(settings, update, {
205
+ bind: settings.channel === "lark" ? "always" : "if-missing"
206
+ });
207
+ if (settings.channel !== "lark" && settings.binding.boundUserId !== update.fromUserId) {
236
208
  await reply(settings, update.fromUserId, "This VCM gateway is already bound to another user.");
237
209
  await recordMessageStatus("inbound", "ignored", update.text, "unbound user");
238
210
  return;
@@ -267,11 +239,19 @@ export function createGatewayService(deps) {
267
239
  }
268
240
  }
269
241
  async function saveInboundMetadata(settings, update, options = {}) {
242
+ const bindMode = options.bind ?? "never";
270
243
  return deps.settings.saveSettings({
271
244
  ...settings,
272
245
  binding: {
273
246
  ...settings.binding,
274
- boundUserId: options.bindIfMissing ? settings.binding.boundUserId ?? update.fromUserId : settings.binding.boundUserId,
247
+ boundUserId: bindMode === "always"
248
+ ? update.fromUserId
249
+ : bindMode === "if-missing"
250
+ ? settings.binding.boundUserId ?? update.fromUserId
251
+ : settings.binding.boundUserId,
252
+ loginUserId: bindMode === "always"
253
+ ? update.fromUserId
254
+ : settings.binding.loginUserId,
275
255
  contextTokens: update.contextToken
276
256
  ? {
277
257
  ...settings.binding.contextTokens,
@@ -731,24 +711,18 @@ export function createGatewayService(deps) {
731
711
  async function statusText(settings) {
732
712
  const synced = await syncDesktopContext(settings);
733
713
  const project = await deps.projectService.getCurrentProject();
714
+ const bindingLine = synced.channel === "lark"
715
+ ? `Active Lark chat: ${synced.binding.boundUserId ? "yes" : "none"}`
716
+ : `Binding: ${synced.binding.boundUserId ? "bound" : "not bound"}`;
734
717
  return [
735
718
  `Gateway: ${synced.enabled ? "on" : "off"}${isRunning() ? " / polling" : ""}`,
736
- `Binding: ${synced.binding.boundUserId ? "bound" : "not bound"}`,
719
+ bindingLine,
737
720
  `Translation: ${synced.translationEnabled ? "on" : "off"}`,
738
721
  `Project: ${project?.repoRoot ?? synced.currentProjectId ?? "none"}`,
739
722
  `Task: ${synced.currentTaskSlug ?? "none"}`,
740
723
  `Last poll: ${synced.lastPollStatus.state}${synced.lastPollStatus.error ? ` (${synced.lastPollStatus.error})` : ""}`
741
724
  ].join("\n");
742
725
  }
743
- function parseLarkPairingCommand(text) {
744
- const match = text.trim().match(/^\/bind\s+([A-Z0-9]{6,12})$/i);
745
- return match?.[1]?.toUpperCase() ?? null;
746
- }
747
- function isValidPairingCode(settings, code) {
748
- const expected = settings.binding.pairingCode?.toUpperCase();
749
- const expiresAt = settings.binding.pairingCodeExpiresAt;
750
- return Boolean(expected && expected === code.toUpperCase() && expiresAt && Date.parse(expiresAt) > Date.now());
751
- }
752
726
  return {
753
727
  async start() {
754
728
  await ensurePolling();
@@ -788,40 +762,6 @@ export function createGatewayService(deps) {
788
762
  const settings = await deps.settings.resetBinding();
789
763
  return deps.settings.expose(settings, isRunning());
790
764
  },
791
- async createPairingCode() {
792
- const settings = await deps.settings.loadSettings();
793
- if (settings.channel !== "lark") {
794
- throw new VcmError({
795
- code: "GATEWAY_PAIRING_UNSUPPORTED",
796
- message: "Pairing codes are only available for Lark Gateway.",
797
- statusCode: 409
798
- });
799
- }
800
- if (!settings.binding.appId || !settings.binding.appSecret) {
801
- throw new VcmError({
802
- code: "GATEWAY_LARK_CONFIG_MISSING",
803
- message: "Complete Lark QR Setup before creating a pairing code.",
804
- statusCode: 409
805
- });
806
- }
807
- const code = randomPairingCode();
808
- const expiresAt = new Date(Date.now() + PAIRING_CODE_TTL_MS).toISOString();
809
- const nextSettings = await deps.settings.saveSettings({
810
- ...settings,
811
- binding: {
812
- ...settings.binding,
813
- pairingCode: code,
814
- pairingCodeExpiresAt: expiresAt
815
- },
816
- updatedAt: now()
817
- });
818
- await ensurePolling();
819
- return {
820
- code,
821
- expiresAt,
822
- status: deps.settings.expose(nextSettings, isRunning())
823
- };
824
- },
825
765
  async startQrLogin() {
826
766
  const settings = await deps.settings.loadSettings();
827
767
  const channel = resolveChannel(settings);
@@ -938,17 +878,25 @@ export function createGatewayService(deps) {
938
878
  message: "Lark QR setup expired. Start a new setup."
939
879
  };
940
880
  }
941
- const result = await larkRegistration.poll({
881
+ const confirmDeadline = Date.now() + LARK_REGISTRATION_CONFIRM_TIMEOUT_MS;
882
+ let result = await larkRegistration.poll({
942
883
  domain: larkRegistrationState.domain,
943
884
  deviceCode: larkRegistrationState.deviceCode
944
885
  });
886
+ while (result.status === "wait" && Date.now() < confirmDeadline) {
887
+ await sleep(Math.min(larkRegistrationState.intervalSeconds * 1000, 3_000), new AbortController().signal);
888
+ result = await larkRegistration.poll({
889
+ domain: larkRegistrationState.domain,
890
+ deviceCode: larkRegistrationState.deviceCode
891
+ });
892
+ }
945
893
  if (result.status !== "confirmed") {
946
894
  if (result.status === "expired" || result.status === "failed") {
947
895
  larkRegistrationState = null;
948
896
  }
949
897
  return {
950
898
  status: result.status,
951
- message: result.message
899
+ message: result.message ?? (result.status === "wait" ? "Lark has not returned app credentials yet. Confirm again in a moment." : undefined)
952
900
  };
953
901
  }
954
902
  if (!result.appId || !result.appSecret || !result.domain) {
@@ -970,8 +918,6 @@ export function createGatewayService(deps) {
970
918
  larkOpenId: result.openId ?? null,
971
919
  larkBotName: result.botName ?? null,
972
920
  larkBotOpenId: result.botOpenId ?? null,
973
- pairingCode: null,
974
- pairingCodeExpiresAt: null,
975
921
  getUpdatesBuf: ""
976
922
  },
977
923
  lastPollStatus: {
@@ -1313,14 +1259,6 @@ function formatAheadBehind(project) {
1313
1259
  }
1314
1260
  return ahead > 0 ? `ahead ${ahead}` : `behind ${behind}`;
1315
1261
  }
1316
- function randomPairingCode() {
1317
- const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
1318
- let out = "";
1319
- for (let index = 0; index < 8; index += 1) {
1320
- out += alphabet[randomInt(alphabet.length)];
1321
- }
1322
- return out;
1323
- }
1324
1262
  function sleep(ms, signal) {
1325
1263
  return new Promise((resolve) => {
1326
1264
  if (signal.aborted) {
@@ -1339,7 +1277,10 @@ function normalizeBaseUrl(input, fallback) {
1339
1277
  if (!trimmed) {
1340
1278
  return fallback;
1341
1279
  }
1342
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
1280
+ if (/^https:\/\/lark:\/\//i.test(trimmed)) {
1281
+ return trimmed.replace(/^https:\/\//i, "").replace(/\/+$/, "");
1282
+ }
1283
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
1343
1284
  return trimmed.replace(/\/+$/, "");
1344
1285
  }
1345
1286
  return `https://${trimmed.replace(/\/+$/, "")}`;
@@ -101,8 +101,7 @@ export function createGatewaySettingsService(deps) {
101
101
  larkOpenId: settings.binding.larkOpenId,
102
102
  larkBotName: settings.binding.larkBotName,
103
103
  larkBotOpenId: settings.binding.larkBotOpenId,
104
- homeChatId: settings.binding.homeChatId,
105
- pairingCodeExpiresAt: settings.binding.pairingCodeExpiresAt
104
+ homeChatId: settings.binding.homeChatId
106
105
  },
107
106
  pendingConfirmations: settings.pendingConfirmations,
108
107
  lastPollStatus: settings.lastPollStatus,
@@ -152,8 +151,6 @@ export function normalizeSettings(input, timestamp, options = {
152
151
  larkBotName: normalizeNullableString(bindingInput.larkBotName),
153
152
  larkBotOpenId: normalizeNullableString(bindingInput.larkBotOpenId),
154
153
  homeChatId: normalizeNullableString(bindingInput.homeChatId),
155
- pairingCode: normalizeNullableString(bindingInput.pairingCode),
156
- pairingCodeExpiresAt: normalizeNullableString(bindingInput.pairingCodeExpiresAt),
157
154
  getUpdatesBuf: typeof bindingInput.getUpdatesBuf === "string" ? bindingInput.getUpdatesBuf : "",
158
155
  contextTokens: isObject(bindingInput.contextTokens)
159
156
  ? normalizeStringRecord(bindingInput.contextTokens)
@@ -191,8 +188,6 @@ function createDefaultBinding(defaultBaseUrl = DEFAULT_BASE_URL) {
191
188
  larkBotName: null,
192
189
  larkBotOpenId: null,
193
190
  homeChatId: null,
194
- pairingCode: null,
195
- pairingCodeExpiresAt: null,
196
191
  getUpdatesBuf: "",
197
192
  contextTokens: {},
198
193
  chatIds: {}
@@ -289,7 +284,10 @@ function normalizeBaseUrl(input, fallback = DEFAULT_BASE_URL) {
289
284
  if (!raw) {
290
285
  return fallback;
291
286
  }
292
- if (raw.startsWith("http://") || raw.startsWith("https://")) {
287
+ if (/^https:\/\/lark:\/\//i.test(raw)) {
288
+ return raw.replace(/^https:\/\//i, "").replace(/\/+$/, "");
289
+ }
290
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) {
293
291
  return raw.replace(/\/+$/, "");
294
292
  }
295
293
  return `https://${raw.replace(/\/+$/, "")}`;