larkin 0.5.7 → 0.5.8
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.8
|
|
4
|
+
|
|
5
|
+
Lark setup now reconciles requested tenant permissions with actual grants and links missing scopes to the same application in the developer console. Required permissions still block setup until granted; optional permissions and event/callback verification are reported separately. Existing-application authorization preserves and checks its App ID before recording any capability state.
|
|
6
|
+
|
|
3
7
|
## 0.5.5
|
|
4
8
|
|
|
5
9
|
Pi uses a small Larkin-owned tmux Bash extension when tmux is available on macOS/Linux. Commands preserve their working directory, including non-Git paths with spaces; a foreground wait can return while the same command continues. The returned task ID supports inspection and cancellation, and background completion resumes the Agent. Native Windows and environments without tmux retain Pi's native Bash.
|
|
@@ -16,7 +16,7 @@ import { collectSetupAgentChoice, missingRuntimeInstallMessage, terminalSetupQue
|
|
|
16
16
|
import { documentCommentSubscriptionCapability, markDocumentCommentSubscriptionVerified, } from "../platform/callback-capability.mjs";
|
|
17
17
|
import { openPlatformHost, parseLarkinTenant, registerAppAccountsHost, } from "../feishu/platform-hosts.mjs";
|
|
18
18
|
import { authorizationUrlFailureMessage, presentAuthorizationUrl } from "./setup-authorization-url.mjs";
|
|
19
|
-
import {
|
|
19
|
+
import { reconcileTenantScopes, tenantScopeRecoveryMessage } from "./tenant-scope-grant.mjs";
|
|
20
20
|
// qrcode-terminal does not publish TypeScript declarations.
|
|
21
21
|
// @ts-expect-error bundled CommonJS dependency
|
|
22
22
|
import qrcodePackage from "qrcode-terminal";
|
|
@@ -755,7 +755,7 @@ Lark 必须用 --tenant lark:授权二维码是官方 /page/cli(begin 仍走
|
|
|
755
755
|
}
|
|
756
756
|
if (!verified)
|
|
757
757
|
throw new Error("Bot identity verification failed");
|
|
758
|
-
const scopeResult = await runIdentityProcess(official.command, [...official.argsPrefix, "api", "GET", "/open-apis/application/v6/scopes"], cliEnv);
|
|
758
|
+
const scopeResult = await runIdentityProcess(official.command, [...official.argsPrefix, "api", "GET", "/open-apis/application/v6/scopes", "--as", "bot"], cliEnv);
|
|
759
759
|
let scopePayload;
|
|
760
760
|
try {
|
|
761
761
|
scopePayload = JSON.parse(scopeResult.stdout || "");
|
|
@@ -763,12 +763,22 @@ Lark 必须用 --tenant lark:授权二维码是官方 /page/cli(begin 仍走
|
|
|
763
763
|
catch {
|
|
764
764
|
scopePayload = null;
|
|
765
765
|
}
|
|
766
|
-
const
|
|
767
|
-
|
|
766
|
+
const grants = reconcileTenantScopes(scopeResult.status === 0 ? scopePayload : null, TENANT_SCOPES);
|
|
767
|
+
const missing = [...grants.missingRequired, ...grants.missingOptional];
|
|
768
|
+
say(grants.verified
|
|
769
|
+
? `Tenant scopes 对账:必需未授予=${grants.missingRequired.join(", ") || "无"};可选未授予=${grants.missingOptional.join(", ") || "无"}`
|
|
770
|
+
: "Tenant scopes 对账未验证:权威 API 失败或响应无效,不能确认任何申请项已授予。");
|
|
771
|
+
if (!grants.verified)
|
|
772
|
+
say("请检查所选 Agent 的受管 bot CLI 与网络后重跑原 larkin setup 命令,保留 --tenant/--runtime 参数;不要重建 Agent,也不要据此修改应用权限。");
|
|
773
|
+
if (grants.verified && !grants.missingRequired.length && grants.missingOptional.length)
|
|
774
|
+
say("! 可选权限未授予,setup 可继续;相关能力仍不可用或未验证。");
|
|
775
|
+
if (missing.length)
|
|
776
|
+
say(tenantScopeRecoveryMessage(tenant, id, missing));
|
|
777
|
+
if (!grants.verified || grants.missingRequired.length > 0) {
|
|
768
778
|
const reason = scopeResult.status !== 0
|
|
769
779
|
? `scopes API 失败 status=${scopeResult.status}`
|
|
770
|
-
: `缺 ${
|
|
771
|
-
throw new Error(`Bot tenant scope 未就绪,${reason};+chat-list
|
|
780
|
+
: !grants.verified ? "scopes API 响应无效" : `缺 ${grants.missingRequired.join(", ")}`;
|
|
781
|
+
throw new Error(`Bot tenant scope 未就绪,${reason};+chat-list 成功不能当作可发群消息。`);
|
|
772
782
|
}
|
|
773
783
|
if (commentSubscription !== "preserve") {
|
|
774
784
|
const reconciled = await applyDocumentCommentSubscription({
|
|
@@ -11,6 +11,7 @@ import { managedOfficialLarkCli } from "../app/agent-lark-cli-workspace.mjs";
|
|
|
11
11
|
import { loadValidatedBotCredential } from "./run-credential-preflight.mjs";
|
|
12
12
|
import { parseLarkinTenant, registerAppAccountsHost } from "../feishu/platform-hosts.mjs";
|
|
13
13
|
import { presentAuthorizationUrl } from "./setup-authorization-url.mjs";
|
|
14
|
+
import { reconcileTenantScopes, tenantScopeRecoveryMessage } from "./tenant-scope-grant.mjs";
|
|
14
15
|
// qrcode-terminal does not publish TypeScript declarations.
|
|
15
16
|
// @ts-expect-error bundled CommonJS dependency
|
|
16
17
|
import qrcodePackage from "qrcode-terminal";
|
|
@@ -114,6 +115,7 @@ export async function main() {
|
|
|
114
115
|
const presented = presentAuthorizationUrl(info.url, {
|
|
115
116
|
tenant: TENANT,
|
|
116
117
|
larkCliVersion: official.command.version,
|
|
118
|
+
expectedAppId: APP_ID,
|
|
117
119
|
});
|
|
118
120
|
if (TENANT === "lark" && /\/page\/launcher(?:\?|$)/.test(presented)) {
|
|
119
121
|
throw new Error("Lark 租户拒绝把 /page/launcher 交给浏览器");
|
|
@@ -144,11 +146,13 @@ export async function main() {
|
|
|
144
146
|
process.exit(1);
|
|
145
147
|
}
|
|
146
148
|
clearTimeout(timeout);
|
|
147
|
-
|
|
149
|
+
if (result?.client_id !== APP_ID)
|
|
150
|
+
throw new Error("授权回传 App ID 与既有应用不匹配;未记录权限或能力状态");
|
|
151
|
+
log(`\n✓ 既有应用授权凭证已回传,appId=${APP_ID};实际权限仍需核验`);
|
|
148
152
|
log(` 已请求增补: ${TENANT_SCOPES.join(", ")}`);
|
|
149
153
|
log(` 已请求事件: ${TENANT_EVENTS.join(", ")}(仍需发布并用真实文档 @Bot 验证)`);
|
|
150
154
|
try {
|
|
151
|
-
markSetupCapabilitiesRequested(config.larkinHome,
|
|
155
|
+
markSetupCapabilitiesRequested(config.larkinHome, APP_ID);
|
|
152
156
|
log(" card.action.trigger 仍是 requested-unverified;请确认发布后执行 interaction callback-probe 并真实点击,验证前不会创建业务交互卡片。");
|
|
153
157
|
log(" document comment capability=publish_or_event_unverified reason=publication_and_real_event_unverified;配置发布与真实事件到达均未验证,不声明生效。");
|
|
154
158
|
log(" document comment reply scope=docs:document.comment:create requested-unverified;同时覆盖 in-thread reply 与 whole-document create_v2 fallback。");
|
|
@@ -158,7 +162,28 @@ export async function main() {
|
|
|
158
162
|
throw error;
|
|
159
163
|
log(" 本机没有该 App 的 bot credential,无法记录 callback readiness;能力保持 missing,请先运行 larkin setup 后再执行 callback-probe。");
|
|
160
164
|
}
|
|
161
|
-
|
|
165
|
+
const managed = resolveManagedOfficialCli(selected, process.env);
|
|
166
|
+
const response = spawnSync(managed.command.command, [...managed.command.argsPrefix, "api", "GET", "/open-apis/application/v6/scopes", "--as", "bot"], { encoding: "utf8", env: managed.env, timeout: 30_000 });
|
|
167
|
+
let payload;
|
|
168
|
+
try {
|
|
169
|
+
payload = JSON.parse(response.stdout || "");
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
payload = null;
|
|
173
|
+
}
|
|
174
|
+
const grants = reconcileTenantScopes(response.status === 0 ? payload : null, TENANT_SCOPES);
|
|
175
|
+
log(grants.verified
|
|
176
|
+
? `Tenant scopes 对账:必需未授予=${grants.missingRequired.join(", ") || "无"};可选未授予=${grants.missingOptional.join(", ") || "无"}`
|
|
177
|
+
: "Tenant scopes 对账未验证:权威 API 失败或响应无效。");
|
|
178
|
+
const missing = [...grants.missingRequired, ...grants.missingOptional];
|
|
179
|
+
if (!grants.verified)
|
|
180
|
+
throw new Error("Bot tenant scope 未验证;请检查所选 Agent 的受管 bot CLI 与网络,再重跑原 setup 命令并保留 --tenant/--runtime;不要据此修改应用权限。");
|
|
181
|
+
if (!grants.missingRequired.length && grants.missingOptional.length)
|
|
182
|
+
log("! 可选权限未授予,setup 可继续;相关能力仍不可用或未验证。");
|
|
183
|
+
if (missing.length)
|
|
184
|
+
log(tenantScopeRecoveryMessage(TENANT, APP_ID, missing));
|
|
185
|
+
if (grants.missingRequired.length)
|
|
186
|
+
throw new Error(`Bot tenant scope 未就绪,缺 ${grants.missingRequired.join(", ")}`);
|
|
162
187
|
}
|
|
163
188
|
if (path.resolve(process.argv[1] || "") === path.resolve(fileURLToPath(import.meta.url))) {
|
|
164
189
|
main().catch((error) => {
|
|
@@ -23,6 +23,9 @@ export function presentAuthorizationUrl(rawUrl, input) {
|
|
|
23
23
|
catch {
|
|
24
24
|
throw new Error("授权 URL 非法");
|
|
25
25
|
}
|
|
26
|
+
if (input.expectedAppId && parsed.searchParams.get("clientID") !== input.expectedAppId) {
|
|
27
|
+
throw new Error("既有应用授权 URL 的 clientID 与目标 App ID 不匹配;未继续授权");
|
|
28
|
+
}
|
|
26
29
|
const userCode = parsed.searchParams.get("user_code") || "";
|
|
27
30
|
// Feishu China still authorizes on /page/launcher. Only International Lark
|
|
28
31
|
// must leave launcher (open.larksuite.com ack 10074 / 链接已失效).
|
|
@@ -36,6 +39,9 @@ export function presentAuthorizationUrl(rawUrl, input) {
|
|
|
36
39
|
const addons = parsed.searchParams.get("addons");
|
|
37
40
|
if (addons)
|
|
38
41
|
presented.searchParams.set("addons", addons);
|
|
42
|
+
const clientID = parsed.searchParams.get("clientID");
|
|
43
|
+
if (clientID)
|
|
44
|
+
presented.searchParams.set("clientID", clientID);
|
|
39
45
|
return presented.toString();
|
|
40
46
|
}
|
|
41
47
|
return parsed.toString();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openPlatformHost } from "../feishu/platform-hosts.mjs";
|
|
1
2
|
/** Tenant scopes the freshness gate needs on the Bot application. */
|
|
2
3
|
export const FRESHNESS_REQUIRED_TENANT_SCOPES = ["im:message.group_msg"];
|
|
3
4
|
function asRows(payload) {
|
|
@@ -12,5 +13,32 @@ function asRows(payload) {
|
|
|
12
13
|
/** grant_status 1 is granted on GET /open-apis/application/v6/scopes. */
|
|
13
14
|
export function missingGrantedTenantScopes(payload, required = FRESHNESS_REQUIRED_TENANT_SCOPES) {
|
|
14
15
|
const rows = asRows(payload);
|
|
15
|
-
return required.filter((name) => !rows.some((row) => row.scope_name === name && row.grant_status === 1));
|
|
16
|
+
return required.filter((name) => !rows.some((row) => row.scope_name === name && row.grant_status === 1 && (row.scope_type === undefined || row.scope_type === "tenant")));
|
|
17
|
+
}
|
|
18
|
+
/** Reconcile intent with the authoritative bot scopes response, never credential return. */
|
|
19
|
+
export function reconcileTenantScopes(payload, requested) {
|
|
20
|
+
const root = payload && typeof payload === "object"
|
|
21
|
+
? payload
|
|
22
|
+
: null;
|
|
23
|
+
const verified = !!root && (root.code === undefined || root.code === 0) && (root.ok === undefined || root.ok === true)
|
|
24
|
+
&& (root.identity === undefined || root.identity === "bot")
|
|
25
|
+
&& (Array.isArray(root.data?.scopes) || Array.isArray(root.scopes));
|
|
26
|
+
const required = FRESHNESS_REQUIRED_TENANT_SCOPES;
|
|
27
|
+
const optional = requested.filter((name) => !required.includes(name));
|
|
28
|
+
return {
|
|
29
|
+
verified,
|
|
30
|
+
missingRequired: verified ? missingGrantedTenantScopes(payload, required) : [],
|
|
31
|
+
missingOptional: verified ? missingGrantedTenantScopes(payload, optional) : [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Official console recovery form used by lark-cli; scopes are tenant permissions. */
|
|
35
|
+
export function tenantScopeRecoveryMessage(tenant, appId, scopes) {
|
|
36
|
+
const url = new URL(`${openPlatformHost(tenant)}/app/${encodeURIComponent(appId)}/auth`);
|
|
37
|
+
url.searchParams.set("q", scopes.join(","));
|
|
38
|
+
url.searchParams.set("op_from", "openapi");
|
|
39
|
+
url.searchParams.set("token_type", "tenant");
|
|
40
|
+
return `保留同一个 App ID ${appId},由应用 owner 打开开发者后台权限管理:${url.toString()}\n`
|
|
41
|
+
+ "完成平台要求的权限确认、管理员审批或版本发布,再重新核验实际授予状态。\n"
|
|
42
|
+
+ `完成后重跑原 larkin setup 命令,保留 --tenant ${tenant} 和 --runtime 参数(可加 --no-start),在网页选择同一个已有机器人并保留原 Runtime 配置,不要重建 Agent。\n`
|
|
43
|
+
+ "凭证回传不代表附加权限已授予;平台可能未应用 addons,不能仅靠重新扫码或重建应用修复。事件与 callbacks 仍需各自验证。";
|
|
16
44
|
}
|