larkin 0.5.11 → 0.5.13
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/dist/feishu/host-shell.cjs +5 -1
- package/dist/feishu/host-shell.mjs +6 -2
- package/dist/runtime/provider-error-classifier.cjs +25 -0
- package/dist/runtime/provider-error-classifier.mjs +24 -0
- package/dist/setup/bot-register.mjs +6 -3
- package/dist/setup/grant-scopes.mjs +6 -3
- package/dist/setup/tenant-scope-grant.mjs +27 -0
- package/package.json +1 -1
|
@@ -1484,7 +1484,11 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1484
1484
|
}
|
|
1485
1485
|
else {
|
|
1486
1486
|
const category = message.event.errorCategory || "provider";
|
|
1487
|
-
|
|
1487
|
+
// Surface the provider's finish reason when the Runtime text names one we
|
|
1488
|
+
// recognize (allow-listed token only; raw Runtime text never reaches status).
|
|
1489
|
+
const finishReason = (0, provider_error_classifier_js_1.providerFinishReason)(message.event.message);
|
|
1490
|
+
const detail = finishReason ? ` (provider finish_reason=${finishReason})` : "";
|
|
1491
|
+
hostState.recordStatusError(agent, `${category}: Runtime input failed ${message.event.retryable ? "retryably" : "non-retryably"}${detail}; inspect delivery health and Runtime/provider configuration`);
|
|
1488
1492
|
}
|
|
1489
1493
|
}
|
|
1490
1494
|
};
|
|
@@ -17,7 +17,7 @@ import { HostChannelBusiness } from "./host-channel-business.mjs";
|
|
|
17
17
|
import { HostInteractionOrchestrator } from "./interaction-orchestrator.mjs";
|
|
18
18
|
import { targetFor } from "./message-policy.mjs";
|
|
19
19
|
import { authFailureAppliesTo, parsePersistedAuthFailure, providerAuthenticationFailureReadiness, readinessForPersistedAuthFailure, RuntimePrerequisiteError, } from "../runtime/runtime-readiness.mjs";
|
|
20
|
-
import { safeProviderDiagnostic } from "../runtime/provider-error-classifier.mjs";
|
|
20
|
+
import { providerFinishReason, safeProviderDiagnostic } from "../runtime/provider-error-classifier.mjs";
|
|
21
21
|
import { readDocumentCommentSubscription, verifyCallbackProbe } from "../platform/callback-capability.mjs";
|
|
22
22
|
import { loadConfig, resolveInboxAuditSchedule, resolveMentionPolicy } from "../platform/config.mjs";
|
|
23
23
|
import { processCommandToken } from "../app/internal-command.mjs";
|
|
@@ -1444,7 +1444,11 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1444
1444
|
}
|
|
1445
1445
|
else {
|
|
1446
1446
|
const category = message.event.errorCategory || "provider";
|
|
1447
|
-
|
|
1447
|
+
// Surface the provider's finish reason when the Runtime text names one we
|
|
1448
|
+
// recognize (allow-listed token only; raw Runtime text never reaches status).
|
|
1449
|
+
const finishReason = providerFinishReason(message.event.message);
|
|
1450
|
+
const detail = finishReason ? ` (provider finish_reason=${finishReason})` : "";
|
|
1451
|
+
hostState.recordStatusError(agent, `${category}: Runtime input failed ${message.event.retryable ? "retryably" : "non-retryably"}${detail}; inspect delivery health and Runtime/provider configuration`);
|
|
1448
1452
|
}
|
|
1449
1453
|
}
|
|
1450
1454
|
};
|
|
@@ -4,6 +4,7 @@ exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = exports.CANONICAL_CONTEXT_WI
|
|
|
4
4
|
exports.safeProviderDiagnostic = safeProviderDiagnostic;
|
|
5
5
|
exports.buildStrictProviderErrorInput = buildStrictProviderErrorInput;
|
|
6
6
|
exports.classifyStrictProviderError = classifyStrictProviderError;
|
|
7
|
+
exports.providerFinishReason = providerFinishReason;
|
|
7
8
|
exports.CODEX_ERROR_PREFIX = "Codex error: ";
|
|
8
9
|
exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
|
|
9
10
|
exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
|
|
@@ -51,3 +52,27 @@ function classifyStrictProviderError(error) {
|
|
|
51
52
|
}
|
|
52
53
|
return undefined;
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Finish reasons we are willing to quote into user-visible status. The list is
|
|
57
|
+
* deliberately closed: Runtime/provider text is never echoed, so only a token
|
|
58
|
+
* validated against this vocabulary can reach `status`.
|
|
59
|
+
*/
|
|
60
|
+
const SURFACED_FINISH_REASONS = new Set([
|
|
61
|
+
"stop", "length", "content_filter", "tool_calls", "function_call",
|
|
62
|
+
"end_turn", "max_tokens", "stop_sequence", "tool_use", "refusal",
|
|
63
|
+
"safety", "recitation", "blocklist", "prohibited_content", "spii", "malformed_function_call", "other",
|
|
64
|
+
"error", "timeout", "rate_limit", "overloaded", "cancelled", "aborted",
|
|
65
|
+
]);
|
|
66
|
+
/**
|
|
67
|
+
* Narrow, allow-listed extraction of a provider finish reason from Runtime text
|
|
68
|
+
* (e.g. "Provider finish_reason: content_filter"). Returns the validated token
|
|
69
|
+
* only — the surrounding payload never leaves this function, so a status line
|
|
70
|
+
* can say why a reply was withheld without echoing raw Runtime data.
|
|
71
|
+
*/
|
|
72
|
+
function providerFinishReason(value) {
|
|
73
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 8_192)
|
|
74
|
+
return null;
|
|
75
|
+
const match = /(?:finish[_-]?reason|rawstopreason)\s*[:=]\s*"?([A-Za-z_]{2,32})"?/i.exec(value);
|
|
76
|
+
const token = match?.[1]?.toLowerCase() ?? "";
|
|
77
|
+
return SURFACED_FINISH_REASONS.has(token) ? token : null;
|
|
78
|
+
}
|
|
@@ -45,3 +45,27 @@ export function classifyStrictProviderError(error) {
|
|
|
45
45
|
}
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Finish reasons we are willing to quote into user-visible status. The list is
|
|
50
|
+
* deliberately closed: Runtime/provider text is never echoed, so only a token
|
|
51
|
+
* validated against this vocabulary can reach `status`.
|
|
52
|
+
*/
|
|
53
|
+
const SURFACED_FINISH_REASONS = new Set([
|
|
54
|
+
"stop", "length", "content_filter", "tool_calls", "function_call",
|
|
55
|
+
"end_turn", "max_tokens", "stop_sequence", "tool_use", "refusal",
|
|
56
|
+
"safety", "recitation", "blocklist", "prohibited_content", "spii", "malformed_function_call", "other",
|
|
57
|
+
"error", "timeout", "rate_limit", "overloaded", "cancelled", "aborted",
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Narrow, allow-listed extraction of a provider finish reason from Runtime text
|
|
61
|
+
* (e.g. "Provider finish_reason: content_filter"). Returns the validated token
|
|
62
|
+
* only — the surrounding payload never leaves this function, so a status line
|
|
63
|
+
* can say why a reply was withheld without echoing raw Runtime data.
|
|
64
|
+
*/
|
|
65
|
+
export function providerFinishReason(value) {
|
|
66
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 8_192)
|
|
67
|
+
return null;
|
|
68
|
+
const match = /(?:finish[_-]?reason|rawstopreason)\s*[:=]\s*"?([A-Za-z_]{2,32})"?/i.exec(value);
|
|
69
|
+
const token = match?.[1]?.toLowerCase() ?? "";
|
|
70
|
+
return SURFACED_FINISH_REASONS.has(token) ? token : null;
|
|
71
|
+
}
|
|
@@ -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 { reconcileTenantScopes, tenantScopeRecoveryMessage } from "./tenant-scope-grant.mjs";
|
|
19
|
+
import { missingOptionalScopeImpacts, 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";
|
|
@@ -770,8 +770,11 @@ Lark 必须用 --tenant lark:授权二维码是官方 /page/cli(begin 仍走
|
|
|
770
770
|
: "Tenant scopes 对账未验证:权威 API 失败或响应无效,不能确认任何申请项已授予。");
|
|
771
771
|
if (!grants.verified)
|
|
772
772
|
say("请检查所选 Agent 的受管 bot CLI 与网络后重跑原 larkin setup 命令,保留 --tenant/--runtime 参数;不要重建 Agent,也不要据此修改应用权限。");
|
|
773
|
-
if (grants.verified && !grants.missingRequired.length && grants.missingOptional.length)
|
|
774
|
-
say("! 可选权限未授予,setup
|
|
773
|
+
if (grants.verified && !grants.missingRequired.length && grants.missingOptional.length) {
|
|
774
|
+
say("! 可选权限未授予,setup 可继续;以下能力可能受限(实际以平台校验为准):");
|
|
775
|
+
for (const impact of missingOptionalScopeImpacts(grants.missingOptional))
|
|
776
|
+
say(` - ${impact}`);
|
|
777
|
+
}
|
|
775
778
|
if (missing.length)
|
|
776
779
|
say(tenantScopeRecoveryMessage(tenant, id, missing));
|
|
777
780
|
if (!grants.verified || grants.missingRequired.length > 0) {
|
|
@@ -11,7 +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
|
+
import { missingOptionalScopeImpacts, reconcileTenantScopes, tenantScopeRecoveryMessage } from "./tenant-scope-grant.mjs";
|
|
15
15
|
// qrcode-terminal does not publish TypeScript declarations.
|
|
16
16
|
// @ts-expect-error bundled CommonJS dependency
|
|
17
17
|
import qrcodePackage from "qrcode-terminal";
|
|
@@ -178,8 +178,11 @@ export async function main() {
|
|
|
178
178
|
const missing = [...grants.missingRequired, ...grants.missingOptional];
|
|
179
179
|
if (!grants.verified)
|
|
180
180
|
throw new Error("Bot tenant scope 未验证;请检查所选 Agent 的受管 bot CLI 与网络,再重跑原 setup 命令并保留 --tenant/--runtime;不要据此修改应用权限。");
|
|
181
|
-
if (!grants.missingRequired.length && grants.missingOptional.length)
|
|
182
|
-
log("! 可选权限未授予,setup
|
|
181
|
+
if (!grants.missingRequired.length && grants.missingOptional.length) {
|
|
182
|
+
log("! 可选权限未授予,setup 可继续;以下能力可能受限(实际以平台校验为准):");
|
|
183
|
+
for (const impact of missingOptionalScopeImpacts(grants.missingOptional))
|
|
184
|
+
log(` - ${impact}`);
|
|
185
|
+
}
|
|
183
186
|
if (missing.length)
|
|
184
187
|
log(tenantScopeRecoveryMessage(TENANT, APP_ID, missing));
|
|
185
188
|
if (grants.missingRequired.length)
|
|
@@ -31,6 +31,33 @@ export function reconcileTenantScopes(payload, requested) {
|
|
|
31
31
|
missingOptional: verified ? missingGrantedTenantScopes(payload, optional) : [],
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Capability each requested-but-optional tenant scope backs. The freshness gate
|
|
36
|
+
* keeps `im:message.group_msg` as the only blocking requirement, but a missing
|
|
37
|
+
* optional scope still degrades a named capability at runtime — an im:chat* gap
|
|
38
|
+
* surfaces as "成员表拉取失败" in the daemon log, for example. Setup reports the
|
|
39
|
+
* impact beside the scope names so the gap is visible before the first run.
|
|
40
|
+
*/
|
|
41
|
+
const OPTIONAL_SCOPE_CAPABILITIES = [
|
|
42
|
+
{ capability: "群与成员信息(成员姓名/群名解析)", scopes: ["im:chat:readonly", "im:chat.group_info:readonly", "im:chat.members:read"] },
|
|
43
|
+
{ capability: "群管理(建群/改名/成员变更/退群)", scopes: ["im:chat", "im:chat:create", "im:chat:update", "im:chat.members:write_only", "im:chat:operate_as_owner"] },
|
|
44
|
+
{ capability: "云文档评论事件与回复", scopes: ["drive:drive", "docs:document.comment:read", "docs:document.comment:create"] },
|
|
45
|
+
{ capability: "应用可用范围自动设为全员可见", scopes: ["admin:app.visibility"] },
|
|
46
|
+
{ capability: "发送者签名中的工号(employee_id)", scopes: ["contact:user.employee_id:readonly"] },
|
|
47
|
+
{ capability: "机器人消息搜索", scopes: ["search:message"] },
|
|
48
|
+
{ capability: "消息收发与资源(基本消息能力)", scopes: ["im:message", "im:message.p2p_msg:readonly", "im:message.group_at_msg:readonly", "im:message:send_as_bot", "im:resource"] },
|
|
49
|
+
];
|
|
50
|
+
/** Missing optional scopes grouped by the capability they degrade, in declaration order. */
|
|
51
|
+
export function missingOptionalScopeImpacts(missing) {
|
|
52
|
+
const names = new Set(missing);
|
|
53
|
+
const lines = OPTIONAL_SCOPE_CAPABILITIES.flatMap(({ capability, scopes }) => {
|
|
54
|
+
const hit = scopes.filter((scope) => names.has(scope));
|
|
55
|
+
return hit.length ? [`${capability}:${hit.join(", ")}`] : [];
|
|
56
|
+
});
|
|
57
|
+
const covered = new Set(OPTIONAL_SCOPE_CAPABILITIES.flatMap(({ scopes }) => scopes));
|
|
58
|
+
const rest = missing.filter((scope) => !covered.has(scope));
|
|
59
|
+
return rest.length ? [...lines, `其他权限:${rest.join(", ")}`] : lines;
|
|
60
|
+
}
|
|
34
61
|
/** Official console recovery form used by lark-cli; scopes are tenant permissions. */
|
|
35
62
|
export function tenantScopeRecoveryMessage(tenant, appId, scopes) {
|
|
36
63
|
const url = new URL(`${openPlatformHost(tenant)}/app/${encodeURIComponent(appId)}/auth`);
|