larkin 0.5.16 → 0.5.18
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 +4 -0
- package/README.md +11 -0
- package/dist/agent/config-cli-contract.cjs +3 -1
- package/dist/agent/config-cli-contract.mjs +3 -1
- package/dist/agent/context-prompt.cjs +7 -7
- package/dist/agent/context-prompt.mjs +7 -7
- package/dist/app/agent-cli.mjs +19 -2
- package/dist/app/agent-config.mjs +33 -4
- package/dist/app/cli.mjs +16 -0
- package/dist/app/lark-cli.mjs +77 -119
- package/dist/app/lark.mjs +14 -0
- package/dist/app/runtime-agent-config.mjs +15 -4
- package/dist/app/runtime-context.mjs +17 -0
- package/dist/dashboard/dashboard-config-controller.mjs +11 -0
- package/dist/dashboard/web/assets/dashboard.js +15 -15
- package/dist/feishu/host-shell.cjs +11 -1
- package/dist/feishu/host-shell.mjs +12 -2
- package/dist/feishu/im-freshness-adapter.cjs +2 -2
- package/dist/feishu/im-freshness-adapter.mjs +2 -2
- package/dist/platform/config.cjs +85 -2
- package/dist/platform/config.mjs +83 -2
- package/dist/runtime/runtime-host.cjs +112 -9
- package/dist/runtime/runtime-host.mjs +112 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.18
|
|
4
|
+
|
|
5
|
+
The Feishu processing-eye OnIt reaction is now off by default. New installs and existing configs that omit `processingEye` no longer auto-react on inbound messages after upgrade; there is no migration prompt. Opt back in with `larkin config processing-eye global on`, a per-Agent `on`/`off`/`inherit` override, or the Dashboard 处理中表情 toggle. Explicit `enabled: false` and a missing key both skip the reactions API.
|
|
6
|
+
|
|
3
7
|
## 0.5.8
|
|
4
8
|
|
|
5
9
|
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.
|
package/README.md
CHANGED
|
@@ -114,6 +114,17 @@ larkin config inbox-audit global off
|
|
|
114
114
|
|
|
115
115
|
When enabled, audit only revisits originally wake-eligible human group/topic work and does not wake a model for an empty work list. Reading an audit list does not complete it: the managed Agent follows the returned inspection instructions and explicitly confirms the receipt after checking. A failed check remains retryable; newer messages cannot be completed by an older receipt. Old audit-index records without proven eligibility or observation identity are ignored; ordinary Inbox messages and conversation history are preserved.
|
|
116
116
|
|
|
117
|
+
### Optional processing eye (OnIt)
|
|
118
|
+
|
|
119
|
+
The Feishu "processing eye" (OnIt reaction on inbound messages while the Agent works) is **off by default**. A missing `processingEye` key is treated as off, so upgrading to this version stops the auto-reaction without a migration prompt. In the Dashboard, use **Global settings → 处理中表情** or the selected Agent's **Configuration → 处理中表情**. The same settings are available through the CLI:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
larkin config processing-eye global on
|
|
123
|
+
larkin config processing-eye agent off --agent <App-ID>
|
|
124
|
+
larkin config processing-eye agent inherit --agent <App-ID>
|
|
125
|
+
larkin config processing-eye global off
|
|
126
|
+
```
|
|
127
|
+
|
|
117
128
|
<details>
|
|
118
129
|
<summary>Windows support and optional autostart</summary>
|
|
119
130
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CONFIG_CLI_VALUES = exports.CONFIG_CLI_USAGE = exports.CONFIG_CLI_OPERATIONS = void 0;
|
|
4
|
-
exports.CONFIG_CLI_OPERATIONS = Object.freeze(["show", "runtime", "model", "effort", "mention", "inbox-audit", "apply"]);
|
|
4
|
+
exports.CONFIG_CLI_OPERATIONS = Object.freeze(["show", "runtime", "model", "effort", "mention", "inbox-audit", "processing-eye", "apply"]);
|
|
5
5
|
exports.CONFIG_CLI_USAGE = Object.freeze([
|
|
6
6
|
"larkin config show [--agent <App ID>] [--chat <oc_id>] [--json]",
|
|
7
7
|
"larkin config runtime <runtime> [--model <model>] [--agent <App ID>]",
|
|
@@ -12,6 +12,8 @@ exports.CONFIG_CLI_USAGE = Object.freeze([
|
|
|
12
12
|
"larkin config mention chat <oc_id> <inherit|require|free> [--agent <App ID>]",
|
|
13
13
|
"larkin config inbox-audit global <on|off> [--interval <15m|1h>]",
|
|
14
14
|
"larkin config inbox-audit agent <inherit|on|off> [--agent <App ID>] [--interval <15m|inherit>]",
|
|
15
|
+
"larkin config processing-eye global <on|off>",
|
|
16
|
+
"larkin config processing-eye agent <inherit|on|off> [--agent <App ID>]",
|
|
15
17
|
"larkin config apply [--agent <App ID>]",
|
|
16
18
|
]);
|
|
17
19
|
exports.CONFIG_CLI_VALUES = Object.freeze({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const CONFIG_CLI_OPERATIONS = Object.freeze(["show", "runtime", "model", "effort", "mention", "inbox-audit", "apply"]);
|
|
1
|
+
export const CONFIG_CLI_OPERATIONS = Object.freeze(["show", "runtime", "model", "effort", "mention", "inbox-audit", "processing-eye", "apply"]);
|
|
2
2
|
export const CONFIG_CLI_USAGE = Object.freeze([
|
|
3
3
|
"larkin config show [--agent <App ID>] [--chat <oc_id>] [--json]",
|
|
4
4
|
"larkin config runtime <runtime> [--model <model>] [--agent <App ID>]",
|
|
@@ -9,6 +9,8 @@ export const CONFIG_CLI_USAGE = Object.freeze([
|
|
|
9
9
|
"larkin config mention chat <oc_id> <inherit|require|free> [--agent <App ID>]",
|
|
10
10
|
"larkin config inbox-audit global <on|off> [--interval <15m|1h>]",
|
|
11
11
|
"larkin config inbox-audit agent <inherit|on|off> [--agent <App ID>] [--interval <15m|inherit>]",
|
|
12
|
+
"larkin config processing-eye global <on|off>",
|
|
13
|
+
"larkin config processing-eye agent <inherit|on|off> [--agent <App ID>]",
|
|
12
14
|
"larkin config apply [--agent <App ID>]",
|
|
13
15
|
]);
|
|
14
16
|
export const CONFIG_CLI_VALUES = Object.freeze({
|
|
@@ -12,11 +12,11 @@ const agent_cli_capabilities_js_1 = require("./agent-cli-capabilities.cjs");
|
|
|
12
12
|
* 1. 明确目标与边界:每条规则写明“何时触发 / 做什么 / 禁止什么”,避免歧义与过度推断。
|
|
13
13
|
* 2. 结构性信号优先于推断:能用来源/目标字段(如 thread target)做权威判定的,不要依赖模型从措辞推断。
|
|
14
14
|
* 3. 精确 recipe:关键操作给完整命令模板 + 约束(如 exact literal、fail-closed、确定性 --jq 数据流)。
|
|
15
|
-
* 4. 幂等与安全:idempotency
|
|
15
|
+
* 4. 幂等与安全:idempotency、非阻塞 freshness 提示、不可重复提交、脱敏、失败可见(fail visibly)。
|
|
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-
|
|
19
|
+
exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v34";
|
|
20
20
|
/**
|
|
21
21
|
* Pi 长命令引导的唯一来源。仅在当前工具列表出现 tmux-backed bash 时适用;
|
|
22
22
|
* 不声明 tmux 一定可用,也不复述扩展的工具细节。
|
|
@@ -156,7 +156,7 @@ class ContextPromptBuilder {
|
|
|
156
156
|
"Successful history response messages are always at `data.messages`. Never use a chat-wide fallback for a thread target, never merge stderr with `2>&1` before parsing JSON, and never truncate structured output before parsing it.",
|
|
157
157
|
"If the scoped history read fails or its schema is invalid, fail visibly. Do not reuse remembered or hard-coded text to make the task appear successful.",
|
|
158
158
|
"Only a real Feishu `message_id` beginning with `om_` may be passed to `+messages-reply`; `rem_`, `redeliver_`, and every other synthetic ID must never be replied to. Send with a confirmed `chat_id` and never guess a chat id from a display name.",
|
|
159
|
-
"
|
|
159
|
+
"Freshness is a non-blocking reminder: a send, reply, card, recall, or urgent write is never rejected for freshness. After a successful write, stderr may contain a `larkin_notice` with `freshness`; the write already happened. Re-read the exact chat or thread with the Larkin command, and send a short follow-up only when newer context changes the answer. Never resend the same content merely because of this notice.",
|
|
160
160
|
"For ordinary plain-text message bodies, use `--text`, including brief one-line replies, multiline status lines, and paragraphs. Use `--markdown` only when you intentionally need Markdown rendering, such as headings, lists, emphasis, blockquotes, fenced code, or Markdown links.",
|
|
161
161
|
"When a URL must be visible, clickable, or openable by the recipient, include the complete bare `https://...` URL as visible text. Do not rely solely on `[label](URL)`, because Feishu client rendering is unreliable. A label may also be included, but the bare URL must remain present.",
|
|
162
162
|
"Use native `--text` for ordinary plain text and for verbatim preservation, such as logs, literal code, or exact whitespace. Both `--markdown` and `--text` remain supported in the Larkin Runtime.",
|
|
@@ -166,7 +166,7 @@ class ContextPromptBuilder {
|
|
|
166
166
|
`For a common exact send with a confirmed chat id, use the complete schematic recipe \`${executable} im +messages-send --chat-id <confirmed_chat_id> --text '<exact_body_as_one_literal_argument>' --json\`; replace each placeholder with the corresponding confirmed or exact literal value.`,
|
|
167
167
|
`A reply's target is governed by the source's thread membership, a structural Inbox fact, not a guess. When the current Inbox event or poll identifies the source as a thread (\`thread:<chat_id>:<thread_id>\` target, or the polled message carries a \`thread_id\`), your reply MUST stay in that same thread: after the required poll use \`${executable} im +messages-reply --message-id <real_om_message_id> --text '<exact_body_as_one_literal_argument>' --reply-in-thread --json\`. For a chat-level source (no thread) with no explicit in-thread request, reply to the main timeline with the same recipe but omit \`--reply-in-thread\`; replace the placeholders only with the real \`om_\` id returned by that poll and the unchanged exact literal.`,
|
|
168
168
|
`Use the \`--reply-in-thread\` recipe only when the source is a thread or the user or current Inbox event explicitly asks for a topic, in-thread, or thread reply. Never invent a topic request from ordinary reply wording or a bare source message id: the thread decision comes only from the source message's actual thread membership (\`thread:\` target or polled \`thread_id\`) or an explicit request, never from wording alone.`,
|
|
169
|
-
"This exact topic-reply recipe has exactly one post-poll model tool call and must not read or re-read a skill or reference, open help, or perform any other discovery.
|
|
169
|
+
"This exact topic-reply recipe has exactly one post-poll model tool call and must not read or re-read a skill or reference, open help, or perform any other discovery. It uses two total model tool calls including the poll. A later freshness notice is advisory and must not cause a duplicate send.",
|
|
170
170
|
"For tool-sourced exact or verbatim text from a Feishu result, do not copy or retype it into `--text`; you must use the deterministic native `--jq` to `--content` dataflow below.",
|
|
171
171
|
"Choose exactly one source selector from the identifier the task starts with, and never switch selectors after reading. The inner read in the selected composite replaces any separate scoped history read; it must not follow a preview read.",
|
|
172
172
|
"For a task that starts from a known thread and asks for its latest human content, after the required Inbox poll invoke the thread composite directly as the first and only post-poll model tool call. For this thread source, never preview or read the thread separately, extract a message id, switch to `+messages-mget`, or add a second source read.",
|
|
@@ -181,10 +181,10 @@ class ContextPromptBuilder {
|
|
|
181
181
|
`The complete canonical exact send and reply paths above must not be preceded by \`${executable} im +messages-send --help\` or \`${executable} im +messages-reply --help\`; run these two known recipes directly.`,
|
|
182
182
|
"For the known canonical Inbox poll, scoped chat/thread history, and exact send/reply recipes specified here, execute them directly; you must not read or re-read a skill file or reference merely to rediscover or confirm their command syntax.",
|
|
183
183
|
`For a task that starts from an exact group name and asks for user and bot counts, after the required Inbox poll use this known canonical two-read recipe directly: first run \`${executable} im +chat-search --query '<exact_group_name>' --json\`, require one result with an exact name match and confirm its \`oc_\` chat id, then run \`${executable} im chats get --chat-id <confirmed_oc_chat_id> --json\` and answer from its \`user_count\` and \`bot_count\`. This group user/bot counts recipe uses exactly two post-poll business read calls; you must not read or re-read a skill or reference, open help or schema, invoke bare \`lark-cli\`, call \`chat.members\` or \`+chat-members-list\`, fall back to \`+chat-list\`, or add shell filters or output truncation. If the exact name is not a unique visible match, the chat id is not confirmed, or either count is missing, fail visibly without another discovery path or a guess.`,
|
|
184
|
-
"This narrow direct-recipe rule does not waive required skill safety gates, authorization, identity,
|
|
185
|
-
`To recall a message after the required explicit user approval, use \`${executable} im messages delete --message-id <bot-owned_om_id> --yes --json\`. Runtime verifies the original sender is this exact Bot, derives the exact chat/thread target,
|
|
184
|
+
"This narrow direct-recipe rule does not waive required skill safety gates, authorization, identity, or other safety checks. Unknown commands or high-risk operations still require the applicable skill/reference guidance or help.",
|
|
185
|
+
`To recall a message after the required explicit user approval, use \`${executable} im messages delete --message-id <bot-owned_om_id> --yes --json\`. Runtime verifies the original sender is this exact Bot, derives the exact chat/thread target, and refuses cross-Agent or third-party messages. A \`committed=true\` or \`duplicate=true\` result must not be retried; an ambiguous result fails closed and requires user inspection. Never use generic API or bare \`lark-cli\` to bypass recall protection.`,
|
|
186
186
|
"The Larkin wrapper derives the stable per-intent idempotency key; do not pass `--idempotency-key` in an ordinary send or reply recipe. A successful write result carrying `duplicate: true` means the provider returned the earlier delivery for the same derived key: the message was already delivered and no new message was created. Do not resend the same command after a `duplicate: true` result; treat the message as delivered. To deliberately send identical content again as a fresh intent (e.g., a new reminder), pass an explicit new `--idempotency-key`, which the wrapper respects instead of deriving its own.",
|
|
187
|
-
"
|
|
187
|
+
"A `larkin_notice` freshness reminder is written to stderr only after the provider write has completed. Keep the successful write; re-read the target before any follow-up, and only send a revised follow-up if the new context materially changes the answer. A result with `committed=true` or `duplicate=true` must not be repeated; ambiguous termination follows the existing wrapper same-key recovery contract.",
|
|
188
188
|
"For multiline `--markdown` or `--text` content in zsh or bash, prefer shell ANSI-C quoting so the command passes one argument with real newline characters: `--markdown $'First line\\nSecond line'` or `--text $'First line\\nSecond line'`. Putting `\"First line\\nSecond line\"` in ordinary double quotes is wrong: ordinary quotes do not decode `\\n`, so lark-cli and Feishu receive a backslash followed by the letter `n`. A literal multiline argument containing real newline characters is also valid. If ANSI-C-quoted content contains an apostrophe, use a safe shell single-quote splice or the literal-newline form; never use `eval`, `echo`, a temporary file, or unsafe variable interpolation to construct the body.",
|
|
189
189
|
...FEISHU_IM_COMMAND_GROUPS.map(([label, suffixes]) => `- ${label}: ${suffixes.map((suffix) => `\`${executable} ${suffix}\``).join(", ")}.`),
|
|
190
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\`.`,
|
|
@@ -9,11 +9,11 @@ import { agentCliPromptCapabilities } from "./agent-cli-capabilities.mjs";
|
|
|
9
9
|
* 1. 明确目标与边界:每条规则写明“何时触发 / 做什么 / 禁止什么”,避免歧义与过度推断。
|
|
10
10
|
* 2. 结构性信号优先于推断:能用来源/目标字段(如 thread target)做权威判定的,不要依赖模型从措辞推断。
|
|
11
11
|
* 3. 精确 recipe:关键操作给完整命令模板 + 约束(如 exact literal、fail-closed、确定性 --jq 数据流)。
|
|
12
|
-
* 4. 幂等与安全:idempotency
|
|
12
|
+
* 4. 幂等与安全:idempotency、非阻塞 freshness 提示、不可重复提交、脱敏、失败可见(fail visibly)。
|
|
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-
|
|
16
|
+
export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v34";
|
|
17
17
|
/**
|
|
18
18
|
* Pi 长命令引导的唯一来源。仅在当前工具列表出现 tmux-backed bash 时适用;
|
|
19
19
|
* 不声明 tmux 一定可用,也不复述扩展的工具细节。
|
|
@@ -153,7 +153,7 @@ export class ContextPromptBuilder {
|
|
|
153
153
|
"Successful history response messages are always at `data.messages`. Never use a chat-wide fallback for a thread target, never merge stderr with `2>&1` before parsing JSON, and never truncate structured output before parsing it.",
|
|
154
154
|
"If the scoped history read fails or its schema is invalid, fail visibly. Do not reuse remembered or hard-coded text to make the task appear successful.",
|
|
155
155
|
"Only a real Feishu `message_id` beginning with `om_` may be passed to `+messages-reply`; `rem_`, `redeliver_`, and every other synthetic ID must never be replied to. Send with a confirmed `chat_id` and never guess a chat id from a display name.",
|
|
156
|
-
"
|
|
156
|
+
"Freshness is a non-blocking reminder: a send, reply, card, recall, or urgent write is never rejected for freshness. After a successful write, stderr may contain a `larkin_notice` with `freshness`; the write already happened. Re-read the exact chat or thread with the Larkin command, and send a short follow-up only when newer context changes the answer. Never resend the same content merely because of this notice.",
|
|
157
157
|
"For ordinary plain-text message bodies, use `--text`, including brief one-line replies, multiline status lines, and paragraphs. Use `--markdown` only when you intentionally need Markdown rendering, such as headings, lists, emphasis, blockquotes, fenced code, or Markdown links.",
|
|
158
158
|
"When a URL must be visible, clickable, or openable by the recipient, include the complete bare `https://...` URL as visible text. Do not rely solely on `[label](URL)`, because Feishu client rendering is unreliable. A label may also be included, but the bare URL must remain present.",
|
|
159
159
|
"Use native `--text` for ordinary plain text and for verbatim preservation, such as logs, literal code, or exact whitespace. Both `--markdown` and `--text` remain supported in the Larkin Runtime.",
|
|
@@ -163,7 +163,7 @@ export class ContextPromptBuilder {
|
|
|
163
163
|
`For a common exact send with a confirmed chat id, use the complete schematic recipe \`${executable} im +messages-send --chat-id <confirmed_chat_id> --text '<exact_body_as_one_literal_argument>' --json\`; replace each placeholder with the corresponding confirmed or exact literal value.`,
|
|
164
164
|
`A reply's target is governed by the source's thread membership, a structural Inbox fact, not a guess. When the current Inbox event or poll identifies the source as a thread (\`thread:<chat_id>:<thread_id>\` target, or the polled message carries a \`thread_id\`), your reply MUST stay in that same thread: after the required poll use \`${executable} im +messages-reply --message-id <real_om_message_id> --text '<exact_body_as_one_literal_argument>' --reply-in-thread --json\`. For a chat-level source (no thread) with no explicit in-thread request, reply to the main timeline with the same recipe but omit \`--reply-in-thread\`; replace the placeholders only with the real \`om_\` id returned by that poll and the unchanged exact literal.`,
|
|
165
165
|
`Use the \`--reply-in-thread\` recipe only when the source is a thread or the user or current Inbox event explicitly asks for a topic, in-thread, or thread reply. Never invent a topic request from ordinary reply wording or a bare source message id: the thread decision comes only from the source message's actual thread membership (\`thread:\` target or polled \`thread_id\`) or an explicit request, never from wording alone.`,
|
|
166
|
-
"This exact topic-reply recipe has exactly one post-poll model tool call and must not read or re-read a skill or reference, open help, or perform any other discovery.
|
|
166
|
+
"This exact topic-reply recipe has exactly one post-poll model tool call and must not read or re-read a skill or reference, open help, or perform any other discovery. It uses two total model tool calls including the poll. A later freshness notice is advisory and must not cause a duplicate send.",
|
|
167
167
|
"For tool-sourced exact or verbatim text from a Feishu result, do not copy or retype it into `--text`; you must use the deterministic native `--jq` to `--content` dataflow below.",
|
|
168
168
|
"Choose exactly one source selector from the identifier the task starts with, and never switch selectors after reading. The inner read in the selected composite replaces any separate scoped history read; it must not follow a preview read.",
|
|
169
169
|
"For a task that starts from a known thread and asks for its latest human content, after the required Inbox poll invoke the thread composite directly as the first and only post-poll model tool call. For this thread source, never preview or read the thread separately, extract a message id, switch to `+messages-mget`, or add a second source read.",
|
|
@@ -178,10 +178,10 @@ export class ContextPromptBuilder {
|
|
|
178
178
|
`The complete canonical exact send and reply paths above must not be preceded by \`${executable} im +messages-send --help\` or \`${executable} im +messages-reply --help\`; run these two known recipes directly.`,
|
|
179
179
|
"For the known canonical Inbox poll, scoped chat/thread history, and exact send/reply recipes specified here, execute them directly; you must not read or re-read a skill file or reference merely to rediscover or confirm their command syntax.",
|
|
180
180
|
`For a task that starts from an exact group name and asks for user and bot counts, after the required Inbox poll use this known canonical two-read recipe directly: first run \`${executable} im +chat-search --query '<exact_group_name>' --json\`, require one result with an exact name match and confirm its \`oc_\` chat id, then run \`${executable} im chats get --chat-id <confirmed_oc_chat_id> --json\` and answer from its \`user_count\` and \`bot_count\`. This group user/bot counts recipe uses exactly two post-poll business read calls; you must not read or re-read a skill or reference, open help or schema, invoke bare \`lark-cli\`, call \`chat.members\` or \`+chat-members-list\`, fall back to \`+chat-list\`, or add shell filters or output truncation. If the exact name is not a unique visible match, the chat id is not confirmed, or either count is missing, fail visibly without another discovery path or a guess.`,
|
|
181
|
-
"This narrow direct-recipe rule does not waive required skill safety gates, authorization, identity,
|
|
182
|
-
`To recall a message after the required explicit user approval, use \`${executable} im messages delete --message-id <bot-owned_om_id> --yes --json\`. Runtime verifies the original sender is this exact Bot, derives the exact chat/thread target,
|
|
181
|
+
"This narrow direct-recipe rule does not waive required skill safety gates, authorization, identity, or other safety checks. Unknown commands or high-risk operations still require the applicable skill/reference guidance or help.",
|
|
182
|
+
`To recall a message after the required explicit user approval, use \`${executable} im messages delete --message-id <bot-owned_om_id> --yes --json\`. Runtime verifies the original sender is this exact Bot, derives the exact chat/thread target, and refuses cross-Agent or third-party messages. A \`committed=true\` or \`duplicate=true\` result must not be retried; an ambiguous result fails closed and requires user inspection. Never use generic API or bare \`lark-cli\` to bypass recall protection.`,
|
|
183
183
|
"The Larkin wrapper derives the stable per-intent idempotency key; do not pass `--idempotency-key` in an ordinary send or reply recipe. A successful write result carrying `duplicate: true` means the provider returned the earlier delivery for the same derived key: the message was already delivered and no new message was created. Do not resend the same command after a `duplicate: true` result; treat the message as delivered. To deliberately send identical content again as a fresh intent (e.g., a new reminder), pass an explicit new `--idempotency-key`, which the wrapper respects instead of deriving its own.",
|
|
184
|
-
"
|
|
184
|
+
"A `larkin_notice` freshness reminder is written to stderr only after the provider write has completed. Keep the successful write; re-read the target before any follow-up, and only send a revised follow-up if the new context materially changes the answer. A result with `committed=true` or `duplicate=true` must not be repeated; ambiguous termination follows the existing wrapper same-key recovery contract.",
|
|
185
185
|
"For multiline `--markdown` or `--text` content in zsh or bash, prefer shell ANSI-C quoting so the command passes one argument with real newline characters: `--markdown $'First line\\nSecond line'` or `--text $'First line\\nSecond line'`. Putting `\"First line\\nSecond line\"` in ordinary double quotes is wrong: ordinary quotes do not decode `\\n`, so lark-cli and Feishu receive a backslash followed by the letter `n`. A literal multiline argument containing real newline characters is also valid. If ANSI-C-quoted content contains an apostrophe, use a safe shell single-quote splice or the literal-newline form; never use `eval`, `echo`, a temporary file, or unsafe variable interpolation to construct the body.",
|
|
186
186
|
...FEISHU_IM_COMMAND_GROUPS.map(([label, suffixes]) => `- ${label}: ${suffixes.map((suffix) => `\`${executable} ${suffix}\``).join(", ")}.`),
|
|
187
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\`.`,
|
package/dist/app/agent-cli.mjs
CHANGED
|
@@ -119,7 +119,7 @@ function migrationError(group, subcommand) {
|
|
|
119
119
|
if (group === "server")
|
|
120
120
|
return "server 已移除;飞书(Lark)群与消息信息请通过 `larkin im ...` 查询。";
|
|
121
121
|
if (group === "im")
|
|
122
|
-
return "请通过 `larkin im ...` 使用飞书(Lark)命令;Runtime 会自动绑定当前 Bot identity
|
|
122
|
+
return "请通过 `larkin im ...` 使用飞书(Lark)命令;Runtime 会自动绑定当前 Bot identity,并在成功写入后给出非阻塞 freshness 提示。可运行 `larkin im --help` 查看帮助。";
|
|
123
123
|
if (group === "profile" && subcommand !== "show") {
|
|
124
124
|
return "profile 只保留只读的 `larkin profile show`;身份和凭证由 `larkin setup` 管理,不支持 update。";
|
|
125
125
|
}
|
|
@@ -256,6 +256,23 @@ function agentConfigRequest(argv, env, config, agent, dependencies) {
|
|
|
256
256
|
agentId: targetId,
|
|
257
257
|
});
|
|
258
258
|
}
|
|
259
|
+
else if (operation === "processing-eye") {
|
|
260
|
+
const [scope, first] = options.positionals;
|
|
261
|
+
if (scope === "global")
|
|
262
|
+
assertOnlyFlags([]);
|
|
263
|
+
else if (scope === "agent")
|
|
264
|
+
assertOnlyFlags(["--agent"]);
|
|
265
|
+
else
|
|
266
|
+
assertOnlyFlags([]);
|
|
267
|
+
if (options.positionals.length !== 2) {
|
|
268
|
+
throw new Error("用法: larkin config processing-eye global <on|off> | processing-eye agent <inherit|on|off> [--agent <App ID>]");
|
|
269
|
+
}
|
|
270
|
+
mutation = larkinConfig.processingEyeMutationFromCli({
|
|
271
|
+
scope: scope || "",
|
|
272
|
+
enabled: first,
|
|
273
|
+
agentId: targetId,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
259
276
|
else if (operation === "apply") {
|
|
260
277
|
assertOnlyFlags(["--agent"]);
|
|
261
278
|
if (options.positionals.length)
|
|
@@ -269,7 +286,7 @@ function agentConfigRequest(argv, env, config, agent, dependencies) {
|
|
|
269
286
|
}).catch((error) => { throw new Error(`配置已保存但未应用:${error instanceof Error ? error.message : String(error)}`); });
|
|
270
287
|
}
|
|
271
288
|
else
|
|
272
|
-
throw new Error("config 只支持 show/runtime/model/effort/mention/inbox-audit/apply;运行 larkin config --help");
|
|
289
|
+
throw new Error("config 只支持 show/runtime/model/effort/mention/inbox-audit/processing-eye/apply;运行 larkin config --help");
|
|
273
290
|
const result = larkinConfig.mutateConfig(env, mutation, authority);
|
|
274
291
|
return { ok: true, revision: result.revision, persisted: true, applyState: result.applyState, changedScope: result.changedScope };
|
|
275
292
|
}
|
|
@@ -52,7 +52,7 @@ const die = (message) => {
|
|
|
52
52
|
process.exit(1);
|
|
53
53
|
};
|
|
54
54
|
if (!kind || !["agents", "model", "runtime", "effort", "chats", "config"].includes(kind)) {
|
|
55
|
-
die("用法: larkin agents | larkin config <show|mention|inbox-audit|apply> | larkin model [<id>] | larkin runtime [<id>] [--model <id>] | larkin effort [<level>|clear] | larkin chats [free|strict <oc_id>]");
|
|
55
|
+
die("用法: larkin agents | larkin config <show|mention|inbox-audit|processing-eye|apply> | larkin model [<id>] | larkin runtime [<id>] [--model <id>] | larkin effort [<level>|clear] | larkin chats [free|strict <oc_id>]");
|
|
56
56
|
}
|
|
57
57
|
const allowedValueFlags = {
|
|
58
58
|
agents: new Set(), model: new Set(["--agent"]), runtime: new Set(["--agent", "--model"]),
|
|
@@ -162,13 +162,23 @@ else if (kind === "config") {
|
|
|
162
162
|
if (positionals.length !== 3)
|
|
163
163
|
die("用法: larkin config inbox-audit agent <inherit|on|off> [--agent <App ID>] [--interval <15m|inherit>]");
|
|
164
164
|
}
|
|
165
|
+
else if (operation === "processing-eye" && scope === "global") {
|
|
166
|
+
assertOnlyFlags([]);
|
|
167
|
+
if (positionals.length !== 3)
|
|
168
|
+
die("用法: larkin config processing-eye global <on|off>");
|
|
169
|
+
}
|
|
170
|
+
else if (operation === "processing-eye" && scope === "agent") {
|
|
171
|
+
assertOnlyFlags(["--agent"]);
|
|
172
|
+
if (positionals.length !== 3)
|
|
173
|
+
die("用法: larkin config processing-eye agent <inherit|on|off> [--agent <App ID>]");
|
|
174
|
+
}
|
|
165
175
|
else if (operation === "apply") {
|
|
166
176
|
assertOnlyFlags(["--agent"]);
|
|
167
177
|
if (positionals.length !== 1)
|
|
168
178
|
die("用法: larkin config apply [--agent <App ID>]");
|
|
169
179
|
}
|
|
170
180
|
else {
|
|
171
|
-
die("config 只支持 show/mention/inbox-audit/apply;运行 larkin config --help");
|
|
181
|
+
die("config 只支持 show/mention/inbox-audit/processing-eye/apply;运行 larkin config --help");
|
|
172
182
|
}
|
|
173
183
|
}
|
|
174
184
|
const loaded = (() => {
|
|
@@ -352,15 +362,20 @@ if (kind === "config") {
|
|
|
352
362
|
say(`全局群消息策略=${String(view.mentionPolicy)}(free 会唤醒所有继承它的 Agent)`);
|
|
353
363
|
const inboxAudit = view.inboxAudit;
|
|
354
364
|
say(`全局 Inbox Audit=${inboxAudit?.enabled ? "on" : "off"} interval=${larkinConfig.formatInboxAuditInterval(Number(inboxAudit?.intervalMs || larkinConfig.DEFAULT_INBOX_AUDIT_INTERVAL_MS))}(默认关闭;未 @ 的 require 群消息不会进入审计)`);
|
|
365
|
+
const processingEye = view.processingEye;
|
|
366
|
+
say(`全局处理中表情=${processingEye?.enabled ? "on" : "off"}(默认关闭;缺 key 视为关闭,升级后不再自动点 OnIt)`);
|
|
355
367
|
for (const item of view.agents) {
|
|
356
368
|
const mention = item.mention;
|
|
357
369
|
const agentAudit = item.inboxAudit;
|
|
370
|
+
const agentEye = item.processingEye;
|
|
358
371
|
const apply = item.apply;
|
|
359
|
-
say(`agent=${item.agentId} runtime=${item.runtime} model=${item.model} effort=${item.effort || "default"} mention=${mention.override} inbox-audit=${agentAudit?.override?.enabled || "inherit"} apply=${apply.applyState || "unknown"}`);
|
|
372
|
+
say(`agent=${item.agentId} runtime=${item.runtime} model=${item.model} effort=${item.effort || "default"} mention=${mention.override} inbox-audit=${agentAudit?.override?.enabled || "inherit"} processing-eye=${agentEye?.override?.enabled || "inherit"} apply=${apply.applyState || "unknown"}`);
|
|
360
373
|
if (mention.chat)
|
|
361
374
|
say(` chat=${mention.chat.chatId} override=${mention.chat.override} effective=${mention.chat.effective} source=${mention.chat.source}`);
|
|
362
375
|
if (agentAudit?.effective)
|
|
363
376
|
say(` inbox-audit effective=${agentAudit.effective.enabled ? "on" : "off"} interval=${larkinConfig.formatInboxAuditInterval(Number(agentAudit.effective.intervalMs || larkinConfig.DEFAULT_INBOX_AUDIT_INTERVAL_MS))}`);
|
|
377
|
+
if (agentEye?.effective)
|
|
378
|
+
say(` processing-eye effective=${agentEye.effective.enabled ? "on" : "off"}`);
|
|
364
379
|
}
|
|
365
380
|
}
|
|
366
381
|
process.exit(0);
|
|
@@ -405,6 +420,20 @@ if (kind === "config") {
|
|
|
405
420
|
}
|
|
406
421
|
process.exit(0);
|
|
407
422
|
}
|
|
423
|
+
if (operation === "processing-eye") {
|
|
424
|
+
try {
|
|
425
|
+
const result = larkinConfig.mutateConfig(process.env, larkinConfig.processingEyeMutationFromCli({
|
|
426
|
+
scope: scope || "",
|
|
427
|
+
enabled: first,
|
|
428
|
+
agentId: scope === "agent" ? requireTarget() : (selected || keys[0] || ""),
|
|
429
|
+
}), authority);
|
|
430
|
+
say(JSON.stringify({ ok: true, revision: result.revision, persisted: result.persisted, applyState: result.applyState, changedScope: result.changedScope }, null, 2));
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
die(error instanceof Error ? error.message : String(error));
|
|
434
|
+
}
|
|
435
|
+
process.exit(0);
|
|
436
|
+
}
|
|
408
437
|
if (operation === "apply") {
|
|
409
438
|
const agentId = requireTarget();
|
|
410
439
|
const expectedSignature = larkinConfig.runtimeConfigSignature(config, agentId);
|
|
@@ -424,7 +453,7 @@ if (kind === "config") {
|
|
|
424
453
|
}
|
|
425
454
|
process.exit(0);
|
|
426
455
|
}
|
|
427
|
-
die("config 只支持 show/mention/inbox-audit/apply");
|
|
456
|
+
die("config 只支持 show/mention/inbox-audit/processing-eye/apply");
|
|
428
457
|
}
|
|
429
458
|
let key = flagAgent;
|
|
430
459
|
if (!key && runtimeAgentId)
|
package/dist/app/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { CONFIG_CLI_USAGE } from "../agent/config-cli-contract.mjs";
|
|
9
9
|
import { internalCommandSpec } from "./internal-command.mjs";
|
|
10
10
|
import { packageVersion } from "../platform/build-info.mjs";
|
|
11
|
+
import { isLarkinRuntimeContext } from "./runtime-context.mjs";
|
|
11
12
|
const command = process.argv[2] || "help";
|
|
12
13
|
let rest = process.argv.slice(3);
|
|
13
14
|
const PACKAGE_VERSION = packageVersion(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."));
|
|
@@ -36,6 +37,18 @@ const routes = {
|
|
|
36
37
|
};
|
|
37
38
|
const runtimeAgentAuthority = typeof process.env.LARKIN_AGENT_ID === "string"
|
|
38
39
|
&& process.env.LARKIN_AGENT_ID.trim().length > 0;
|
|
40
|
+
const runtimeContext = isLarkinRuntimeContext(process.env);
|
|
41
|
+
// `config` deliberately remains a public operator surface: it supports global
|
|
42
|
+
// and cross-Agent configuration, and it never falls through to `activeAgent`.
|
|
43
|
+
const runtimeScopedCommands = new Set(["inbox", "reminder", "interaction", "profile", "comment"]);
|
|
44
|
+
const missingRuntimeAuthority = (name) => {
|
|
45
|
+
console.error(`larkin: Runtime Agent authority is missing for "${command}" (LARKIN_AGENT_ID is required). `
|
|
46
|
+
+ `Runtime context was detected (${name}); it will not fall back to the active Agent.`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
};
|
|
49
|
+
if (!runtimeAgentAuthority && runtimeScopedCommands.has(command)) {
|
|
50
|
+
missingRuntimeAuthority("agent-scoped command");
|
|
51
|
+
}
|
|
39
52
|
const runtimeAgentCommand = runtimeAgentAuthority
|
|
40
53
|
&& ["inbox", "reminder", "interaction", "profile", "config"].includes(command);
|
|
41
54
|
if (runtimeAgentCommand)
|
|
@@ -74,6 +87,7 @@ Examples:
|
|
|
74
87
|
larkin config runtime pi --agent cli_x --model default
|
|
75
88
|
larkin config mention chat oc_x free --agent cli_x
|
|
76
89
|
larkin config inbox-audit global on --interval 15m
|
|
90
|
+
larkin config processing-eye global on
|
|
77
91
|
|
|
78
92
|
Credentials, internal paths, serverId, activeAgent, and raw config are never exposed here.`,
|
|
79
93
|
session: `Usage: larkin session reset --agent <App ID> --json [--wait-ready <seconds>]
|
|
@@ -107,6 +121,8 @@ const retiredPublicCommands = new Set(["pi-auth", "pi-distribution"]);
|
|
|
107
121
|
const wantsHelp = command === "help" || command === "--help" || command === "-h"
|
|
108
122
|
|| (!runtimeAgentAuthority && command.startsWith("-"));
|
|
109
123
|
if (!routes[command] && !wantsHelp && !retiredPublicCommands.has(command)) {
|
|
124
|
+
if (runtimeContext && !runtimeAgentAuthority)
|
|
125
|
+
missingRuntimeAuthority("Runtime marker");
|
|
110
126
|
routes[command] = runtimeAgentAuthority ? ["lark-cli", command] : ["lark", command];
|
|
111
127
|
}
|
|
112
128
|
if (!routes[command]) {
|