vibe-coding-master 0.4.35 → 0.4.36

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.
@@ -21,10 +21,11 @@ export function createLarkChannel(options = {}) {
21
21
  ready: Promise.resolve(),
22
22
  error: null
23
23
  };
24
+ const sdkDomain = input.domain === "lark" ? Lark.Domain.Lark : Lark.Domain.Feishu;
24
25
  const client = new Lark.Client({
25
26
  appId: input.appId,
26
27
  appSecret: input.appSecret,
27
- domain: input.domain
28
+ domain: sdkDomain
28
29
  });
29
30
  const dispatcher = new Lark.EventDispatcher({}).register({
30
31
  "im.message.receive_v1": async (data) => {
@@ -40,7 +41,7 @@ export function createLarkChannel(options = {}) {
40
41
  const wsClient = new Lark.WSClient({
41
42
  appId: input.appId,
42
43
  appSecret: input.appSecret,
43
- domain: input.domain,
44
+ domain: sdkDomain,
44
45
  source: "vcm-gateway",
45
46
  autoReconnect: true,
46
47
  loggerLevel: options.loggerLevel,
@@ -1,115 +1,146 @@
1
- const ACCOUNTS_BASE_URLS = {
2
- feishu: "https://accounts.feishu.cn",
3
- lark: "https://accounts.larksuite.com"
1
+ import { randomUUID } from "node:crypto";
2
+ const ACCOUNTS_HOSTS = {
3
+ feishu: "accounts.feishu.cn",
4
+ lark: "accounts.larksuite.com"
4
5
  };
5
6
  const OPEN_BASE_URLS = {
6
7
  feishu: "https://open.feishu.cn",
7
8
  lark: "https://open.larksuite.com"
8
9
  };
9
- const REGISTRATION_PATH = "/oauth/v1/app/registration";
10
10
  const REQUEST_TIMEOUT_MS = 10_000;
11
+ const DEFAULT_REGISTRATION_EXPIRES_IN_SECONDS = 600;
12
+ const DEFAULT_REGISTRATION_INTERVAL_SECONDS = 5;
11
13
  export function createLarkRegistrationClient(deps = {}) {
12
14
  const fetchImpl = deps.fetch ?? fetch;
13
- async function postRegistration(domain, body) {
14
- const url = `${ACCOUNTS_BASE_URLS[domain]}${REGISTRATION_PATH}`;
15
- const payload = await fetchJson(fetchImpl, url, {
16
- method: "POST",
17
- headers: {
18
- "content-type": "application/x-www-form-urlencoded"
19
- },
20
- body: new URLSearchParams(body)
15
+ let registerAppPromise = deps.registerApp ? Promise.resolve(deps.registerApp) : null;
16
+ let active = null;
17
+ async function getRegisterApp() {
18
+ registerAppPromise ??= import("@larksuiteoapi/node-sdk").then((sdk) => {
19
+ if (typeof sdk.registerApp !== "function") {
20
+ throw new Error("Lark SDK does not expose registerApp.");
21
+ }
22
+ return sdk.registerApp;
21
23
  });
22
- return payload;
24
+ return registerAppPromise;
23
25
  }
24
26
  return {
25
- async init(domain) {
26
- const payload = await postRegistration(domain, { action: "init" });
27
- const data = responseData(payload);
28
- const methods = Array.isArray(data.supported_auth_methods)
29
- ? data.supported_auth_methods.filter((value) => typeof value === "string")
30
- : [];
31
- if (!methods.includes("client_secret")) {
32
- throw new Error(`Lark QR setup does not support client_secret auth. Supported methods: ${methods.join(", ") || "none"}.`);
33
- }
27
+ async init() {
28
+ await getRegisterApp();
34
29
  },
35
30
  async begin(domain) {
36
- const payload = await postRegistration(domain, {
37
- action: "begin",
38
- archetype: "PersonalAgent",
39
- auth_method: "client_secret",
40
- request_user_info: "open_id"
31
+ active?.controller.abort();
32
+ const registerApp = await getRegisterApp();
33
+ const controller = new AbortController();
34
+ const id = `lark-registration-${Date.now()}-${randomUUID()}`;
35
+ const session = {
36
+ id,
37
+ domain,
38
+ controller,
39
+ expiresAtMs: Date.now() + DEFAULT_REGISTRATION_EXPIRES_IN_SECONDS * 1000,
40
+ qrUrl: "",
41
+ intervalSeconds: DEFAULT_REGISTRATION_INTERVAL_SECONDS,
42
+ result: null,
43
+ message: undefined,
44
+ promise: Promise.resolve()
45
+ };
46
+ active = session;
47
+ let qrReadySettled = false;
48
+ let resolveQrReady = () => undefined;
49
+ let rejectQrReady = () => undefined;
50
+ const qrReady = new Promise((resolve, reject) => {
51
+ resolveQrReady = resolve;
52
+ rejectQrReady = reject;
41
53
  });
42
- const data = responseData(payload);
43
- const deviceCode = stringOrNull(data.device_code);
44
- if (!deviceCode) {
45
- throw new Error("Lark QR setup did not return a device_code.");
46
- }
47
- const qrUrl = appendQrTrackingParams(stringOrNull(data.verification_uri_complete) ?? "");
48
- if (!qrUrl) {
49
- throw new Error("Lark QR setup did not return a QR URL.");
50
- }
54
+ session.promise = (async () => {
55
+ try {
56
+ const result = await registerApp({
57
+ domain: ACCOUNTS_HOSTS[domain],
58
+ larkDomain: ACCOUNTS_HOSTS.lark,
59
+ source: "vcm",
60
+ signal: controller.signal,
61
+ onQRCodeReady(info) {
62
+ const qrUrl = info.url;
63
+ const expireIn = positiveNumberOr(info.expireIn, DEFAULT_REGISTRATION_EXPIRES_IN_SECONDS);
64
+ session.qrUrl = qrUrl;
65
+ session.expiresAtMs = Date.now() + expireIn * 1000;
66
+ if (!qrReadySettled) {
67
+ qrReadySettled = true;
68
+ resolveQrReady({ url: qrUrl, expireIn });
69
+ }
70
+ },
71
+ onStatusChange(info) {
72
+ session.message = formatSdkStatus(info);
73
+ }
74
+ });
75
+ const appId = stringOrNull(result.client_id);
76
+ const appSecret = stringOrNull(result.client_secret);
77
+ const resultDomain = result.user_info?.tenant_brand === "lark" ? "lark" : domain;
78
+ if (!appId || !appSecret) {
79
+ session.result = {
80
+ status: "failed",
81
+ message: "Lark QR setup completed without app credentials."
82
+ };
83
+ return;
84
+ }
85
+ const bot = await probeBot(fetchImpl, {
86
+ appId,
87
+ appSecret,
88
+ domain: resultDomain
89
+ });
90
+ session.result = {
91
+ status: "confirmed",
92
+ appId,
93
+ appSecret,
94
+ domain: resultDomain,
95
+ openId: stringOrNull(result.user_info?.open_id),
96
+ botName: bot.botName,
97
+ botOpenId: bot.botOpenId
98
+ };
99
+ }
100
+ catch (error) {
101
+ const mapped = mapSdkError(error);
102
+ session.result = mapped;
103
+ if (!qrReadySettled) {
104
+ qrReadySettled = true;
105
+ rejectQrReady(new Error(mapped.message ?? "Lark QR setup failed."));
106
+ }
107
+ }
108
+ })();
109
+ const qrInfo = await qrReady;
51
110
  return {
52
111
  domain,
53
- deviceCode,
54
- qrUrl,
55
- userCode: stringOrNull(data.user_code),
56
- intervalSeconds: positiveNumberOr(data.interval, 5),
57
- expiresInSeconds: positiveNumberOr(data.expire_in, 600)
112
+ deviceCode: id,
113
+ qrUrl: qrInfo.url,
114
+ userCode: extractUserCode(qrInfo.url),
115
+ intervalSeconds: session.intervalSeconds,
116
+ expiresInSeconds: qrInfo.expireIn
58
117
  };
59
118
  },
60
119
  async poll(input) {
61
- const payload = await postRegistration(input.domain, {
62
- action: "poll",
63
- device_code: input.deviceCode,
64
- tp: "ob_app"
65
- });
66
- const data = responseData(payload);
67
- const userInfo = isObject(data.user_info) ? data.user_info : {};
68
- const tenantBrand = stringOrNull(userInfo.tenant_brand);
69
- const domain = tenantBrand === "lark" ? "lark" : input.domain;
70
- const appId = stringOrNull(data.client_id);
71
- const appSecret = stringOrNull(data.client_secret);
72
- if (appId && appSecret) {
73
- const bot = await probeBot(fetchImpl, {
74
- appId,
75
- appSecret,
76
- domain
77
- });
120
+ if (!active || active.id !== input.deviceCode) {
78
121
  return {
79
- status: "confirmed",
80
- appId,
81
- appSecret,
82
- domain,
83
- openId: stringOrNull(userInfo.open_id),
84
- botName: bot.botName,
85
- botOpenId: bot.botOpenId
122
+ status: "expired",
123
+ message: "Lark QR setup session is no longer active. Start a new setup."
86
124
  };
87
125
  }
88
- const error = stringOrNull(data.error) ?? stringOrNull(payload.error);
89
- if (error === "expired_token") {
90
- return { status: "expired", message: "Lark QR setup expired. Start a new setup." };
126
+ if (active.result) {
127
+ return active.result;
91
128
  }
92
- if (error === "access_denied") {
93
- return { status: "failed", message: "Lark QR setup was denied." };
129
+ if (Date.now() > active.expiresAtMs) {
130
+ active.controller.abort();
131
+ active.result = {
132
+ status: "expired",
133
+ message: "Lark QR setup expired. Start a new setup."
134
+ };
135
+ return active.result;
94
136
  }
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);
101
137
  return {
102
138
  status: "wait",
103
- message: error && error !== "authorization_pending"
104
- ? [error, errorDescription].filter(Boolean).join(": ")
105
- : undefined
139
+ message: active.message
106
140
  };
107
141
  }
108
142
  };
109
143
  }
110
- function responseData(payload) {
111
- return isObject(payload.data) ? payload.data : payload;
112
- }
113
144
  async function probeBot(fetchImpl, input) {
114
145
  try {
115
146
  const tokenPayload = await fetchJson(fetchImpl, `${OPEN_BASE_URLS[input.domain]}/open-apis/auth/v3/tenant_access_token/internal`, {
@@ -184,29 +215,53 @@ async function fetchJson(fetchImpl, url, init = {}) {
184
215
  }
185
216
  }
186
217
  function formatPayloadError(payload) {
187
- const data = responseData(payload);
188
218
  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,
219
+ stringOrNull(payload.error),
220
+ stringOrNull(payload.error_description),
221
+ stringOrNull(payload.message),
222
+ stringOrNull(payload.msg),
194
223
  typeof payload.code === "number" || typeof payload.code === "string" ? `code ${payload.code}` : null
195
224
  ].filter(Boolean).join(": ");
196
225
  return message ? ` (${message})` : "";
197
226
  }
198
- function appendQrTrackingParams(value) {
199
- if (!value) {
200
- return "";
227
+ function mapSdkError(error) {
228
+ const code = isObject(error) ? stringOrNull(error.code) : null;
229
+ const description = isObject(error) ? stringOrNull(error.description) : null;
230
+ if (code === "expired_token") {
231
+ return { status: "expired", message: "Lark QR setup expired. Start a new setup." };
232
+ }
233
+ if (code === "access_denied") {
234
+ return { status: "failed", message: "Lark QR setup was denied." };
201
235
  }
236
+ if (code === "abort") {
237
+ return { status: "failed", message: "Lark QR setup was cancelled." };
238
+ }
239
+ if (error instanceof Error) {
240
+ return { status: "failed", message: error.message };
241
+ }
242
+ return {
243
+ status: "failed",
244
+ message: description ?? (code ? `Lark QR setup failed: ${code}` : "Lark QR setup failed.")
245
+ };
246
+ }
247
+ function formatSdkStatus(info) {
248
+ if (info.status === "domain_switched") {
249
+ return "Detected a Lark tenant; continuing setup on the Lark domain.";
250
+ }
251
+ if (info.status === "slow_down") {
252
+ return info.interval
253
+ ? `Lark requested slower setup polling; retrying every ${info.interval} seconds.`
254
+ : "Lark requested slower setup polling.";
255
+ }
256
+ return undefined;
257
+ }
258
+ function extractUserCode(qrUrl) {
202
259
  try {
203
- const url = new URL(value);
204
- url.searchParams.set("from", "vcm");
205
- url.searchParams.set("tp", "vcm");
206
- return url.toString();
260
+ const value = new URL(qrUrl).searchParams.get("user_code");
261
+ return stringOrNull(value);
207
262
  }
208
263
  catch {
209
- return `${value}${value.includes("?") ? "&" : "?"}from=vcm&tp=vcm`;
264
+ return null;
210
265
  }
211
266
  }
212
267
  function positiveNumberOr(input, fallback) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.4.35",
3
+ "version": "0.4.36",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [