vibe-coding-master 0.4.34 → 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
  });
@@ -201,12 +201,12 @@ function appendQrTrackingParams(value) {
201
201
  }
202
202
  try {
203
203
  const url = new URL(value);
204
- url.searchParams.set("from", "hermes");
205
- url.searchParams.set("tp", "hermes");
204
+ url.searchParams.set("from", "vcm");
205
+ url.searchParams.set("tp", "vcm");
206
206
  return url.toString();
207
207
  }
208
208
  catch {
209
- return `${value}${value.includes("?") ? "&" : "?"}from=hermes&tp=hermes`;
209
+ return `${value}${value.includes("?") ? "&" : "?"}from=vcm&tp=vcm`;
210
210
  }
211
211
  }
212
212
  function positiveNumberOr(input, fallback) {
@@ -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,7 +7,6 @@ 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;
@@ -203,37 +201,10 @@ export function createGatewayService(deps) {
203
201
  });
204
202
  return;
205
203
  }
206
- if (settings.channel === "lark" && !settings.binding.boundUserId) {
207
- settings = await saveInboundMetadata(settings, update);
208
- const pairing = parseLarkPairingCommand(update.text);
209
- if (!pairing || !isValidPairingCode(settings, pairing)) {
210
- await reply(settings, update.fromUserId, "Lark Gateway is not paired. Generate a pairing code in desktop VCM, then send /bind CODE here.");
211
- await recordMessageStatus("inbound", "ignored", update.text, "lark gateway not paired");
212
- return;
213
- }
214
- await deps.settings.saveSettings({
215
- ...settings,
216
- binding: {
217
- ...settings.binding,
218
- boundUserId: update.fromUserId,
219
- loginUserId: update.fromUserId,
220
- pairingCode: null,
221
- pairingCodeExpiresAt: null,
222
- chatIds: update.chatId
223
- ? {
224
- ...settings.binding.chatIds,
225
- [update.fromUserId]: update.chatId
226
- }
227
- : settings.binding.chatIds
228
- },
229
- updatedAt: now()
230
- });
231
- await reply(await deps.settings.loadSettings(), update.fromUserId, "Lark Gateway bound. Send /help for available commands.");
232
- await recordMessageStatus("inbound", "ok", update.text, undefined, "bind");
233
- return;
234
- }
235
- settings = await saveInboundMetadata(settings, update, { bindIfMissing: true });
236
- 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) {
237
208
  await reply(settings, update.fromUserId, "This VCM gateway is already bound to another user.");
238
209
  await recordMessageStatus("inbound", "ignored", update.text, "unbound user");
239
210
  return;
@@ -268,11 +239,19 @@ export function createGatewayService(deps) {
268
239
  }
269
240
  }
270
241
  async function saveInboundMetadata(settings, update, options = {}) {
242
+ const bindMode = options.bind ?? "never";
271
243
  return deps.settings.saveSettings({
272
244
  ...settings,
273
245
  binding: {
274
246
  ...settings.binding,
275
- 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,
276
255
  contextTokens: update.contextToken
277
256
  ? {
278
257
  ...settings.binding.contextTokens,
@@ -732,24 +711,18 @@ export function createGatewayService(deps) {
732
711
  async function statusText(settings) {
733
712
  const synced = await syncDesktopContext(settings);
734
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"}`;
735
717
  return [
736
718
  `Gateway: ${synced.enabled ? "on" : "off"}${isRunning() ? " / polling" : ""}`,
737
- `Binding: ${synced.binding.boundUserId ? "bound" : "not bound"}`,
719
+ bindingLine,
738
720
  `Translation: ${synced.translationEnabled ? "on" : "off"}`,
739
721
  `Project: ${project?.repoRoot ?? synced.currentProjectId ?? "none"}`,
740
722
  `Task: ${synced.currentTaskSlug ?? "none"}`,
741
723
  `Last poll: ${synced.lastPollStatus.state}${synced.lastPollStatus.error ? ` (${synced.lastPollStatus.error})` : ""}`
742
724
  ].join("\n");
743
725
  }
744
- function parseLarkPairingCommand(text) {
745
- const match = text.trim().match(/^\/bind\s+([A-Z0-9]{6,12})$/i);
746
- return match?.[1]?.toUpperCase() ?? null;
747
- }
748
- function isValidPairingCode(settings, code) {
749
- const expected = settings.binding.pairingCode?.toUpperCase();
750
- const expiresAt = settings.binding.pairingCodeExpiresAt;
751
- return Boolean(expected && expected === code.toUpperCase() && expiresAt && Date.parse(expiresAt) > Date.now());
752
- }
753
726
  return {
754
727
  async start() {
755
728
  await ensurePolling();
@@ -789,40 +762,6 @@ export function createGatewayService(deps) {
789
762
  const settings = await deps.settings.resetBinding();
790
763
  return deps.settings.expose(settings, isRunning());
791
764
  },
792
- async createPairingCode() {
793
- const settings = await deps.settings.loadSettings();
794
- if (settings.channel !== "lark") {
795
- throw new VcmError({
796
- code: "GATEWAY_PAIRING_UNSUPPORTED",
797
- message: "Pairing codes are only available for Lark Gateway.",
798
- statusCode: 409
799
- });
800
- }
801
- if (!settings.binding.appId || !settings.binding.appSecret) {
802
- throw new VcmError({
803
- code: "GATEWAY_LARK_CONFIG_MISSING",
804
- message: "Complete Lark QR Setup before creating a pairing code.",
805
- statusCode: 409
806
- });
807
- }
808
- const code = randomPairingCode();
809
- const expiresAt = new Date(Date.now() + PAIRING_CODE_TTL_MS).toISOString();
810
- const nextSettings = await deps.settings.saveSettings({
811
- ...settings,
812
- binding: {
813
- ...settings.binding,
814
- pairingCode: code,
815
- pairingCodeExpiresAt: expiresAt
816
- },
817
- updatedAt: now()
818
- });
819
- await ensurePolling();
820
- return {
821
- code,
822
- expiresAt,
823
- status: deps.settings.expose(nextSettings, isRunning())
824
- };
825
- },
826
765
  async startQrLogin() {
827
766
  const settings = await deps.settings.loadSettings();
828
767
  const channel = resolveChannel(settings);
@@ -979,8 +918,6 @@ export function createGatewayService(deps) {
979
918
  larkOpenId: result.openId ?? null,
980
919
  larkBotName: result.botName ?? null,
981
920
  larkBotOpenId: result.botOpenId ?? null,
982
- pairingCode: null,
983
- pairingCodeExpiresAt: null,
984
921
  getUpdatesBuf: ""
985
922
  },
986
923
  lastPollStatus: {
@@ -1322,14 +1259,6 @@ function formatAheadBehind(project) {
1322
1259
  }
1323
1260
  return ahead > 0 ? `ahead ${ahead}` : `behind ${behind}`;
1324
1261
  }
1325
- function randomPairingCode() {
1326
- const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
1327
- let out = "";
1328
- for (let index = 0; index < 8; index += 1) {
1329
- out += alphabet[randomInt(alphabet.length)];
1330
- }
1331
- return out;
1332
- }
1333
1262
  function sleep(ms, signal) {
1334
1263
  return new Promise((resolve) => {
1335
1264
  if (signal.aborted) {
@@ -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: {}