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.
@@ -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
- hostState.recordStatusError(agent, `${category}: Runtime input failed ${message.event.retryable ? "retryably" : "non-retryably"}; inspect delivery health and Runtime/provider configuration`);
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
- hostState.recordStatusError(agent, `${category}: Runtime input failed ${message.event.retryable ? "retryably" : "non-retryably"}; inspect delivery health and Runtime/provider configuration`);
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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkin",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [