larkin 0.4.8 → 0.4.10

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.
@@ -17,6 +17,7 @@ import { probeNativeRuntimeReadiness } from "../runtime/runtime-readiness.mjs";
17
17
  import { configureBuiltinPiProviderModel, stageBuiltinPiProvider, validatePiBaseUrl, validateBuiltinPiProviderSelection, listProviderModels, PI_PROVIDER_PRESETS } from "../runtime/pi-provider-config.mjs";
18
18
  import { beginBuiltinPiCredentialTransaction, createOfficialPiCredentialRuntime, createOfficialPiAuthInteraction, createOfficialPiModelRuntime, createOfficialPiLogoutRuntime, createOfficialPiRegistryRuntime, listOfficialPiAuthProviders, logoutOfficialPiProvider, officialPiAuthStatus, runOfficialPiLogin, verifyOfficialPiProviderTurn, } from "../runtime/pi-official-auth.mjs";
19
19
  import { documentCommentSubscriptionCapability, markDocumentCommentSubscriptionVerified, } from "../platform/callback-capability.mjs";
20
+ import { openPlatformHost, parseLarkinTenant, registerAppAccountsHost, } from "../feishu/platform-hosts.mjs";
20
21
  // qrcode-terminal does not publish TypeScript declarations.
21
22
  // @ts-expect-error bundled CommonJS dependency
22
23
  import qrcodePackage from "qrcode-terminal";
@@ -217,6 +218,45 @@ export async function applyDocumentCommentSubscription(input) {
217
218
  throw new Error("document comment local verified-state persistence failed; external subscription rollback was verified");
218
219
  }
219
220
  }
221
+ async function resolveSelectedTenant() {
222
+ const raw = flag("--tenant");
223
+ if (raw !== undefined) {
224
+ const parsed = parseLarkinTenant(raw);
225
+ if (parsed === "feishu" || parsed === "lark")
226
+ return parsed;
227
+ die("--tenant 只支持 feishu 或 lark");
228
+ }
229
+ if (process.stdin.isTTY && process.stdout.isTTY && !testFixture) {
230
+ const questioner = terminalSetupQuestioner();
231
+ try {
232
+ const answer = (await questioner.ask("选择平台(授权二维码必须对应正确品牌;lark-cli --domain 是业务域,品牌是 feishu|lark):\n"
233
+ + " 1. 中国飞书 Feishu(accounts.feishu.cn / https://open.feishu.cn)\n"
234
+ + " 2. International Lark(accounts.larksuite.com / https://open.larksuite.com)\n> ")).trim();
235
+ if (answer === "1" || /^feishu$/i.test(answer))
236
+ return "feishu";
237
+ if (answer === "2" || /^lark$/i.test(answer))
238
+ return "lark";
239
+ die("必须选择 1/feishu 或 2/lark;未开始授权");
240
+ }
241
+ finally {
242
+ questioner.close?.();
243
+ }
244
+ }
245
+ return "feishu";
246
+ }
247
+ function persistTenant(selected, userInfo) {
248
+ const brand = userInfo?.tenant_brand;
249
+ if (brand === undefined || brand === null || brand === "")
250
+ return selected;
251
+ const observed = parseLarkinTenant(brand);
252
+ if (observed !== "feishu" && observed !== "lark") {
253
+ die("授权返回的租户品牌非法;未执行凭证同步、文件写入或 Agent 绑定");
254
+ }
255
+ if (observed !== selected) {
256
+ die(`授权返回的租户 ${observed} 与预先选择的 ${selected} 不一致;未执行凭证同步、文件写入或 Agent 绑定`);
257
+ }
258
+ return selected;
259
+ }
220
260
  function botVerificationRetryable(result) {
221
261
  const text = `${typeof result.stdout === "string" ? result.stdout : ""}\n${typeof result.stderr === "string" ? result.stderr : ""}`;
222
262
  return /\binvalid_client\b|specified app does not exist|EAI_AGAIN|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|fetch failed|temporary network|too many requests|\b(?:429|502|503|504)\b/i.test(text);
@@ -374,7 +414,7 @@ async function fetchBotInfoViaHttp(appId, tenant) {
374
414
  }
375
415
  if (typeof credential.appSecret !== "string" || !credential.appSecret)
376
416
  return null;
377
- const base = tenant === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
417
+ const base = openPlatformHost(tenant === "lark" ? "lark" : "feishu");
378
418
  try {
379
419
  const tokenResponse = await fetch(`${base}/open-apis/auth/v3/tenant_access_token/internal`, {
380
420
  method: "POST",
@@ -424,7 +464,10 @@ export async function main() {
424
464
  say(`setup 内部机器人注册阶段
425
465
 
426
466
  用法:
427
- bun bot-register.mjs --auto
467
+ bun bot-register.mjs --auto [--tenant feishu|lark]
468
+
469
+ --tenant 在授权二维码之前选择品牌。默认 feishu(accounts.feishu.cn);
470
+ Lark 必须用 --tenant lark 或交互选择,授权主机是 accounts.larksuite.com。
428
471
 
429
472
  完成后自动:按返回 appId 判定新旧 → 同步并校验 lark-cli bot 凭证 →
430
473
  原子写 bots/<appId>.json(0600)→ 绑定 Agent。`);
@@ -447,7 +490,8 @@ export async function main() {
447
490
  }
448
491
  if (!autoSelect)
449
492
  die("setup 注册阶段必须由交互式选择流程启动");
450
- say("[setup 1/5] 在网页选择已有机器人或创建新机器人");
493
+ const selectedTenant = await resolveSelectedTenant();
494
+ say(`[setup 1/5] 在${selectedTenant === "lark" ? " International Lark" : "中国飞书 Feishu"}网页选择已有机器人或创建新机器人`);
451
495
  if (commentSubscription === "application") {
452
496
  say(`! 已显式选择 ${commentSubscription} 维度评论订阅:一旦平台状态验证为已订阅,Bot 可见文档中实际送达的每条支持评论都会进入 Inbox 并唤醒 Agent,不要求 @Bot。`);
453
497
  say("! setup 将通过官方 lark-cli 的结构化 API 请求创建该订阅,并以只读 subscription_status 二次核验;不会从意图或事件配置推断订阅已生效。");
@@ -483,6 +527,8 @@ export async function main() {
483
527
  let pollingCount = 0;
484
528
  const result = await registerApp({
485
529
  source: "larkin",
530
+ domain: registerAppAccountsHost(selectedTenant),
531
+ larkDomain: registerAppAccountsHost("lark"),
486
532
  addons: {
487
533
  scopes: { tenant: TENANT_SCOPES },
488
534
  events: { items: { tenant: TENANT_EVENTS } },
@@ -509,9 +555,9 @@ export async function main() {
509
555
  die("授权完成但未取得有效凭证;未执行凭证同步、文件写入或 Agent 绑定");
510
556
  const id = rawId;
511
557
  const secret = rawSecret;
558
+ const tenant = persistTenant(selectedTenant, userInfo);
512
559
  const botsDir = ensureSecureBotsDir();
513
560
  say(`[setup 3/5] 正在按 App ID ${id} 刷新凭证并配置 Agent`);
514
- const tenant = userInfo?.tenant_brand === "lark" ? "lark" : "feishu";
515
561
  const botFile = path.join(botsDir, `${id}.json`);
516
562
  const prior = (() => {
517
563
  try {
@@ -8,6 +8,8 @@ import { registerApp as channelRegisterApp } from "@larksuite/channel";
8
8
  import { markSetupCapabilitiesRequested } from "../platform/callback-capability.mjs";
9
9
  import * as larkinConfig from "../platform/config.mjs";
10
10
  import { managedOfficialLarkCli } from "../app/agent-lark-cli-workspace.mjs";
11
+ import { loadValidatedBotCredential } from "./run-credential-preflight.mjs";
12
+ import { parseLarkinTenant, registerAppAccountsHost } from "../feishu/platform-hosts.mjs";
11
13
  // qrcode-terminal does not publish TypeScript declarations.
12
14
  // @ts-expect-error bundled CommonJS dependency
13
15
  import qrcodePackage from "qrcode-terminal";
@@ -41,7 +43,19 @@ if (explicitAppId && explicitAppId !== selected.feishuAppId)
41
43
  if (!/^cli_[A-Za-z0-9]+$/.test(APP_ID))
42
44
  throw new Error(`无效飞书(Lark) App ID:${APP_ID}`);
43
45
  const SEND_TO = flag("--send-to", "");
44
- const TENANT = flag("--tenant", "feishu");
46
+ const explicitTenant = argv.includes("--tenant") ? flag("--tenant", "") : "";
47
+ if (argv.includes("--tenant") && !parseLarkinTenant(explicitTenant)) {
48
+ throw new Error("--tenant 只支持 feishu 或 lark");
49
+ }
50
+ function storedCredentialTenant() {
51
+ try {
52
+ return loadValidatedBotCredential(path.join(config.larkinHome, "bots"), APP_ID).tenant;
53
+ }
54
+ catch {
55
+ return "feishu";
56
+ }
57
+ }
58
+ const TENANT = parseLarkinTenant(explicitTenant) ?? storedCredentialTenant();
45
59
  const WAIT_MIN = Number(flag("--wait-min", "9"));
46
60
  const URL_FILE = flag("--url-file", "");
47
61
  const TENANT_SCOPES = [
@@ -108,9 +122,8 @@ export async function main() {
108
122
  sendUrlToChat(info.url, minutes);
109
123
  },
110
124
  onStatusChange: (info) => log(`[grant] 状态: ${info.status}`),
125
+ domain: registerAppAccountsHost(TENANT),
111
126
  };
112
- if (TENANT === "lark")
113
- options.domain = "lark";
114
127
  let result;
115
128
  try {
116
129
  result = await registerApp(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkin",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [