larkin 0.5.12 → 0.5.14

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.
@@ -16,7 +16,7 @@ const agent_cli_capabilities_js_1 = require("./agent-cli-capabilities.cjs");
16
16
  * 5. 版本化:每次实质改动 bump LARKIN_STANDING_PROMPT_VERSION,并同步 eval 数据集/graders/断言。
17
17
  * 6. 用 eval 验证:行为变化必须配套固定场景 + rubric(evals/*、test/support/*-grader.mjs、live 测试)。
18
18
  */
19
- exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v32";
19
+ exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v33";
20
20
  /**
21
21
  * Pi 长命令引导的唯一来源。仅在当前工具列表出现 tmux-backed bash 时适用;
22
22
  * 不声明 tmux 一定可用,也不复述扩展的工具细节。
@@ -43,6 +43,8 @@ const FEISHU_IM_COMMAND_GROUPS = [
43
43
  ["Messages", ["im +messages-send", "im +messages-reply", "im messages delete", "im +chat-messages-list", "im +threads-messages-list", "im +messages-mget"]],
44
44
  ["Chats", ["im +chat-list", "im +chat-search", "im chats get"]],
45
45
  ];
46
+ // 云文档 shortcut 与 IM 一样是官方 lark-cli 直通,不属于 Agent CLI 自有命令清单。
47
+ const FEISHU_DOCS_COMMANDS = ["docs +create", "docs +update", "docs +fetch"];
46
48
  function clean(value) {
47
49
  return value.replaceAll("\r", "").trim();
48
50
  }
@@ -187,6 +189,15 @@ class ContextPromptBuilder {
187
189
  ...FEISHU_IM_COMMAND_GROUPS.map(([label, suffixes]) => `- ${label}: ${suffixes.map((suffix) => `\`${executable} ${suffix}\``).join(", ")}.`),
188
190
  `- Attachments: for an attachment-only send/reply, use its native attachment flag without a text body flag; download with \`${executable} im +messages-resources-download\`.`,
189
191
  "",
192
+ "## Feishu docs command map",
193
+ "",
194
+ `Create, read, or update a Feishu cloud document only through these Larkin-owned shortcuts. Identity stays Runtime-locked: use \`${executable}\`, never invoke bare \`lark-cli\`, and never pass \`--agent\`, \`--as user\`, \`--profile\`, or \`--config-dir\`. If a docs command reports a missing scope, relay that error unchanged and ask the user to authorize it; do not bypass the scope boundary or invent a scope grant.`,
195
+ "A missing document URL or token is fail-closed: stop and report the missing locator. Do not guess a token, and do not call any docs shortcut that is absent from this map.",
196
+ `To create a document from supplied Markdown or plain text, run \`${executable} docs +create --doc-format markdown --content '<exact_markdown_body>' --json\`. Pass \`--content\` as one literal argument, using the same ANSI-C quoting rule as message bodies when the body is multiline. For a local file, use \`--content @<file>\` instead of inlining it. Create an empty document only with \`${executable} docs +create --title '<exact_title>' --json\` and no \`--content\`. \`--parent-token\` and \`--parent-position\` are optional and mutually exclusive. Official default \`--doc-format\` is \`xml\`; use \`markdown\` for this ordinary supplied-text recipe.`,
197
+ `To read an existing document, run \`${executable} docs +fetch --doc <doc_url_or_token> --json\`. Add \`--scope outline|range|keyword|section\` only for a partial read. Add \`--detail with-ids\` before an update that needs block ids. Omit \`--scope\` only when the whole document is required.`,
198
+ `To change an existing document, fetch its current content first, then run \`${executable} docs +update --doc <doc_url_or_token> --command <command> --json\`. For a simple inline replacement use \`--command str_replace --pattern '<exact_old>' --content '<exact_new>'\`. To append, use \`--command append --content '<exact_body>'\`. Use \`--command overwrite\` only when the user explicitly asks to replace the whole document. If the fetch fails or the scope is missing, fail closed and do not write from memory.`,
199
+ `- Documents: ${FEISHU_DOCS_COMMANDS.map((suffix) => `\`${executable} ${suffix}\``).join(", ")}.`,
200
+ "",
190
201
  "Do not invent Larkin commands that are absent from this list. Project instructions and memory remain owned by the runtime's native workspace discovery.",
191
202
  ].filter((line, index, all) => line !== "" || (index > 0 && all[index - 1] !== ""));
192
203
  const content = `${sections.join("\n").trim()}\n`;
@@ -13,7 +13,7 @@ import { agentCliPromptCapabilities } from "./agent-cli-capabilities.mjs";
13
13
  * 5. 版本化:每次实质改动 bump LARKIN_STANDING_PROMPT_VERSION,并同步 eval 数据集/graders/断言。
14
14
  * 6. 用 eval 验证:行为变化必须配套固定场景 + rubric(evals/*、test/support/*-grader.mjs、live 测试)。
15
15
  */
16
- export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v32";
16
+ export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v33";
17
17
  /**
18
18
  * Pi 长命令引导的唯一来源。仅在当前工具列表出现 tmux-backed bash 时适用;
19
19
  * 不声明 tmux 一定可用,也不复述扩展的工具细节。
@@ -40,6 +40,8 @@ const FEISHU_IM_COMMAND_GROUPS = [
40
40
  ["Messages", ["im +messages-send", "im +messages-reply", "im messages delete", "im +chat-messages-list", "im +threads-messages-list", "im +messages-mget"]],
41
41
  ["Chats", ["im +chat-list", "im +chat-search", "im chats get"]],
42
42
  ];
43
+ // 云文档 shortcut 与 IM 一样是官方 lark-cli 直通,不属于 Agent CLI 自有命令清单。
44
+ const FEISHU_DOCS_COMMANDS = ["docs +create", "docs +update", "docs +fetch"];
43
45
  function clean(value) {
44
46
  return value.replaceAll("\r", "").trim();
45
47
  }
@@ -184,6 +186,15 @@ export class ContextPromptBuilder {
184
186
  ...FEISHU_IM_COMMAND_GROUPS.map(([label, suffixes]) => `- ${label}: ${suffixes.map((suffix) => `\`${executable} ${suffix}\``).join(", ")}.`),
185
187
  `- Attachments: for an attachment-only send/reply, use its native attachment flag without a text body flag; download with \`${executable} im +messages-resources-download\`.`,
186
188
  "",
189
+ "## Feishu docs command map",
190
+ "",
191
+ `Create, read, or update a Feishu cloud document only through these Larkin-owned shortcuts. Identity stays Runtime-locked: use \`${executable}\`, never invoke bare \`lark-cli\`, and never pass \`--agent\`, \`--as user\`, \`--profile\`, or \`--config-dir\`. If a docs command reports a missing scope, relay that error unchanged and ask the user to authorize it; do not bypass the scope boundary or invent a scope grant.`,
192
+ "A missing document URL or token is fail-closed: stop and report the missing locator. Do not guess a token, and do not call any docs shortcut that is absent from this map.",
193
+ `To create a document from supplied Markdown or plain text, run \`${executable} docs +create --doc-format markdown --content '<exact_markdown_body>' --json\`. Pass \`--content\` as one literal argument, using the same ANSI-C quoting rule as message bodies when the body is multiline. For a local file, use \`--content @<file>\` instead of inlining it. Create an empty document only with \`${executable} docs +create --title '<exact_title>' --json\` and no \`--content\`. \`--parent-token\` and \`--parent-position\` are optional and mutually exclusive. Official default \`--doc-format\` is \`xml\`; use \`markdown\` for this ordinary supplied-text recipe.`,
194
+ `To read an existing document, run \`${executable} docs +fetch --doc <doc_url_or_token> --json\`. Add \`--scope outline|range|keyword|section\` only for a partial read. Add \`--detail with-ids\` before an update that needs block ids. Omit \`--scope\` only when the whole document is required.`,
195
+ `To change an existing document, fetch its current content first, then run \`${executable} docs +update --doc <doc_url_or_token> --command <command> --json\`. For a simple inline replacement use \`--command str_replace --pattern '<exact_old>' --content '<exact_new>'\`. To append, use \`--command append --content '<exact_body>'\`. Use \`--command overwrite\` only when the user explicitly asks to replace the whole document. If the fetch fails or the scope is missing, fail closed and do not write from memory.`,
196
+ `- Documents: ${FEISHU_DOCS_COMMANDS.map((suffix) => `\`${executable} ${suffix}\``).join(", ")}.`,
197
+ "",
187
198
  "Do not invent Larkin commands that are absent from this list. Project instructions and memory remain owned by the runtime's native workspace discovery.",
188
199
  ].filter((line, index, all) => line !== "" || (index > 0 && all[index - 1] !== ""));
189
200
  const content = `${sections.join("\n").trim()}\n`;
@@ -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.12",
3
+ "version": "0.5.14",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [