dsh-lark-bot 0.19.0 → 0.19.2
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/README.md +6 -3
- package/README_EN.md +7 -4
- package/dist/cli.js +64 -19
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +61 -17
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
**基础能力**:
|
|
50
50
|
|
|
51
51
|
- 私聊、群聊、话题(thread)里指挥本机 dsh coding agent,图片 / 文本文件直接发给 bot 即可;
|
|
52
|
-
-
|
|
52
|
+
- 流式过程卡以飞书原生折叠面板实时展示阶段、耗时以及工具名称与状态;完成但存在失败工具时汇总为“已完成(含警告)/Completed with warnings”,不把“任务轮次结束”冒充为“所有工具成功”。完成后最终回答单独发送,支持交互按钮(停止 / 计划门禁 / 审批 / 问答卡);原始推理、工具输入输出与底层错误不会进入卡片;卡片更新失败会有限重试并降级为普通提示,Agent 与最终回答继续,不会拖垮 bridge 进程;
|
|
53
53
|
- Git 仓库内为每个会话自动创建隔离 worktree 项目工作区,多项目互不干扰。
|
|
54
54
|
|
|
55
55
|
**十二项全网独有组合**:
|
|
@@ -154,7 +154,8 @@ Markdown、toast 与旧客户端降级路径同时显示中英文。agent 最终
|
|
|
154
154
|
| `/help` | 查看帮助|
|
|
155
155
|
|
|
156
156
|
每轮 SDK / ACP / Web 请求都会注入结构化、无密钥的频道上下文,并注册官方 runtime Skill
|
|
157
|
-
`dsh-lark-bot
|
|
157
|
+
`dsh-lark-bot`。上下文明确区分 bridge 预处理的用户斜杠命令与 Agent 可调用工具;后者的列表不代表
|
|
158
|
+
前者不存在,Skill 暂时不可加载时仍以 `/help` 为当前版本权威命令入口。API Key、token 与 App Secret 必须经 `/key set <引用名>`、`/secret set …` 或 Agent
|
|
158
159
|
工具 `lark_request_secret` 打开的密码表单提交;普通聊天、旧 `/key set <引用名> <值>` 与
|
|
159
160
|
`--api-key` 不再消费值。表单只允许发起者提交,值直接写入本机受支持目标,不进入 prompt、session、
|
|
160
161
|
任务账本、归档、日志、诊断包或回复。Guardian 安全模式是降级恢复面,不提供该完整配置与密钥工具。
|
|
@@ -247,7 +248,7 @@ guardian 仍只救援其配置的主实例。
|
|
|
247
248
|
|
|
248
249
|
**结果文件直接回传**:SDK / ACP / Web agent 可调用 `lark_send_file`,把当前会话 workspace、实际执行 worktree、当前 scope 归档或实例日志中的文件直接上传到原飞书聊天 / 话题;普通 `/archive [note]` 会在落盘后立即发送 Markdown + JSONL,失败时保留路径并可用 `/archive send <id> [scope|chatId]` 重试或由管理员转发到指定会话。上传只接受普通文件,默认单文件不超过 20 MiB;真实路径必须位于 bridge 计算的会话目录内,runtime 自报 cwd 不能扩大边界。
|
|
249
250
|
|
|
250
|
-
**逐操作审批与 scope 权限策略**:SDK / ACP / Web runtime
|
|
251
|
+
**逐操作审批与 scope 权限策略**:SDK / ACP / Web runtime 在任何本地快速通道和计划门裁决前,先通过鉴权回环同步读取当前 immutable scope 的 `ask|allow|deny`;该 policy-only 查询不创建卡片或进入人类等待传输。`deny` 对低风险与高风险工具都先行拒绝并返回 `permission-policy` 来源;`ask` 对保守只读自省静默放行、对高风险调用弹“允许执行一次 / 拒绝”卡;`allow` 自动放行逐工具审批,但仍不替代高风险任务的计划确认或 Harness 文件沙箱。管理员可用 `/permission allow|deny|ask [scope]` 修改当前聊天内 scope;策略成功落盘后才确认,持久化到 profile 的 `permission-policies.json`(0600),重启不丢并显示在 `/status`。legacy `headless` 不具备工具回调能力。
|
|
251
252
|
|
|
252
253
|
**关键任务计划门禁**:SDK / ACP / Web agent 在修改文件、运行脚本等较大或高风险动作前使用
|
|
253
254
|
`lark_request_plan_approval`;同一 turn 未获批准时,runtime pre-execute 策略会拒绝写入、删除、
|
|
@@ -528,6 +529,8 @@ SDK / ACP 启动会先解析完整 provider/model route:显式双字段优先
|
|
|
528
529
|
`agent-default-model: { provider, model }`;仍无法得到完整 route 时在 bridge/doctor 入口给出本项目的
|
|
529
530
|
明确配置错误,不把空 provider 传给上游 runtime。受管 service 的 install/start/restart 会把旧 env
|
|
530
531
|
文件中的受管键与当前 shell 合并(当前 shell 显式值优先),避免从另一个终端重启时静默丢失已有 route。
|
|
532
|
+
模型目录在进程冷启动时暂时不可达,也会保留该对象形式默认 route 作为最小离线条目;这不会放行
|
|
533
|
+
settings 中没有明确配置的未知模型。
|
|
531
534
|
|
|
532
535
|
启动时会自动查找本机常见的 `@deepseek-ai/dsh` 安装位置。只有自动发现失败或需要指定特殊 profile 时,才需要设置这两个变量。
|
|
533
536
|
|
package/README_EN.md
CHANGED
|
@@ -51,7 +51,7 @@ Bot-owned command help, status/error messages and interactive cards are availabl
|
|
|
51
51
|
**Core**:
|
|
52
52
|
|
|
53
53
|
- Drive your local dsh coding agent from private chats, group chats and threads; images / text files can be sent straight to the bot;
|
|
54
|
-
- A streaming process card with a native collapsible panel for phase, elapsed time, and tool names/statuses
|
|
54
|
+
- A streaming process card with a native collapsible panel for phase, elapsed time, and tool names/statuses. A completed turn containing a failed tool is summarized as **Completed with warnings** in native and legacy views without changing the completed job outcome. Raw reasoning, tool payloads, and underlying errors stay out of the card. The final answer arrives separately, with interactive buttons for stop / plan gate / approval / questions. Failed card patches are retried finitely and degrade to a plain notice—the agent and final reply continue instead of taking down the bridge;
|
|
55
55
|
- Automatic session archival and retention policies; per-session isolated git worktrees inside Git repositories, so multiple projects never interfere with each other.
|
|
56
56
|
|
|
57
57
|
**Twelve exclusive capabilities**:
|
|
@@ -152,7 +152,9 @@ Send a normal message to the bot in Feishu to get started. Common commands:
|
|
|
152
152
|
| `/help` | Show help |
|
|
153
153
|
|
|
154
154
|
Every SDK, ACP, and Web turn receives structured, secret-free channel context and the official runtime
|
|
155
|
-
`dsh-lark-bot` skill.
|
|
155
|
+
`dsh-lark-bot` skill. The context distinguishes bridge-side slash commands from model-callable tools: the tool list
|
|
156
|
+
does not imply that slash commands are absent, and `/help` remains the authoritative command list if the runtime
|
|
157
|
+
skill cannot be loaded. API keys, tokens, and App Secrets must be entered through the owner-only password form opened
|
|
156
158
|
by `/key set <ref>`, `/secret set …`, or `lark_request_secret`. Ordinary chat, legacy `/key set <ref> <value>`, and
|
|
157
159
|
`--api-key` no longer consume values. The value never enters prompts, sessions, jobs, archives, logs, diagnostics,
|
|
158
160
|
or replies. Guardian safe mode is a degraded recovery surface without the full configuration/secret seam.
|
|
@@ -168,7 +170,8 @@ The `/model` card merges the dsh default into its switchable catalogue even when
|
|
|
168
170
|
list omits it, and uses compact distinguishing labels with at most two buttons per mobile row. Provider
|
|
169
171
|
names, models, input modalities, and reasoning-effort options are discovered from the models.dev runtime
|
|
170
172
|
catalogue and cached in memory for 15 minutes. If it is unavailable, only explicit dsh settings and the
|
|
171
|
-
configured default are shown
|
|
173
|
+
configured default are shown, and an object-form `agent-default-model` remains a minimally resolvable offline
|
|
174
|
+
route—there is no hardcoded fallback list and other unknown models remain rejected. Override the feed with
|
|
172
175
|
`DSH_LARK_MODEL_CATALOG_URL`; model commands and the wizard preserve `inputModalities`.
|
|
173
176
|
|
|
174
177
|
**Message-level DSH session sync (`web` adapter)**: `/session` lists metadata only for non-subagent sessions
|
|
@@ -256,7 +259,7 @@ reject `web`, because a shared Web agent broadcast stream cannot isolate session
|
|
|
256
259
|
|
|
257
260
|
**Direct result-file delivery**: SDK / ACP / Web agents can call `lark_send_file` to upload a file from the current session workspace, its actual execution worktree, its scope archive, or the instance logs to the originating Feishu chat/topic. `/archive [note]` uploads its Markdown and JSONL after the durable local write; `/archive send <id> [scope|chatId]` retries locally or lets an admin forward it to a registered session. Only regular files up to 20 MiB are accepted by default. The resolved path must remain inside roots computed by the bridge; a runtime-supplied cwd never expands access.
|
|
258
261
|
|
|
259
|
-
**Per-action approval and scope policy**: the default SDK and Web host enforce a `tools/pre-execute` gate and wire dsh rc.8's official `approval/request` seam into Feishu; ACP uses native `session/request_permission`. The default `ask` policy shows **Allow once** / **Reject
|
|
262
|
+
**Per-action approval and scope policy**: the default SDK and Web host enforce a `tools/pre-execute` gate and wire dsh rc.8's official `approval/request` seam into Feishu; ACP uses native `session/request_permission`. Before any fast path or plan gate, the runtime synchronously queries the immutable scope policy through the authenticated policy-only callback; this response is `ask|allow|deny`, requires no approval outcome, and never creates or waits for a card. The default `ask` policy shows **Allow once** / **Reject** for high-risk calls. An admin may use `/permission allow` to auto-allow tool approvals in the current isolated scope, `/permission deny` to reject them with an explicit chat notice, or `/permission ask` to restore prompts. In member isolation, copy the target from `/status` and use `/permission <policy> <scope>`; cross-chat targets are rejected. Success is confirmed only after the owner-only `permission-policies.json` write completes, so policies survive restarts and appear in `/status`. They never bypass the separate plan gate; legacy `headless` has no tool callback channel.
|
|
260
263
|
|
|
261
264
|
**Plan gate for substantial tasks**: SDK / ACP / Web agents use `lark_request_plan_approval` before file
|
|
262
265
|
changes, scripts, or other substantial/high-risk actions. A runtime pre-execute policy denies writes, deletes,
|
package/dist/cli.js
CHANGED
|
@@ -1247,6 +1247,19 @@ var PUBLIC_MODEL_CATALOG = new ModelsDevCatalog();
|
|
|
1247
1247
|
function isMapLike(value) {
|
|
1248
1248
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1249
1249
|
}
|
|
1250
|
+
function defaultModelSelectionFromSettings(settings) {
|
|
1251
|
+
const section = settings[AGENT_DEFAULT_MODEL_NAMESPACE];
|
|
1252
|
+
if (typeof section === "string") {
|
|
1253
|
+
return { provider: DEEPSEEK_PROVIDER, model: section };
|
|
1254
|
+
}
|
|
1255
|
+
if (isMapLike(section) && typeof section.model === "string") {
|
|
1256
|
+
return {
|
|
1257
|
+
provider: typeof section.provider === "string" ? section.provider : DEEPSEEK_PROVIDER,
|
|
1258
|
+
model: section.model
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
return void 0;
|
|
1262
|
+
}
|
|
1250
1263
|
function deepEqualJson(a, b) {
|
|
1251
1264
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
1252
1265
|
}
|
|
@@ -1490,10 +1503,24 @@ var DshProviderManager = class {
|
|
|
1490
1503
|
error: error instanceof Error ? error.message : String(error)
|
|
1491
1504
|
});
|
|
1492
1505
|
}
|
|
1493
|
-
|
|
1506
|
+
const providers = [
|
|
1494
1507
|
await this.describeDeepseek(settings, catalogProviders),
|
|
1495
1508
|
...await this.describePiAi(settings, catalogProviders)
|
|
1496
1509
|
];
|
|
1510
|
+
const configuredDefault = defaultModelSelectionFromSettings(settings);
|
|
1511
|
+
if (!configuredDefault) return providers;
|
|
1512
|
+
return providers.map((provider) => {
|
|
1513
|
+
if (provider.id !== configuredDefault.provider || provider.models.some((model) => model.id === configuredDefault.model)) return provider;
|
|
1514
|
+
return {
|
|
1515
|
+
...provider,
|
|
1516
|
+
models: [...provider.models, {
|
|
1517
|
+
id: configuredDefault.model,
|
|
1518
|
+
name: void 0,
|
|
1519
|
+
contextWindow: void 0,
|
|
1520
|
+
maxTokens: void 0
|
|
1521
|
+
}]
|
|
1522
|
+
};
|
|
1523
|
+
});
|
|
1497
1524
|
}
|
|
1498
1525
|
async describeDeepseek(settings, catalogProviders) {
|
|
1499
1526
|
const deepseek = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
|
|
@@ -1544,18 +1571,7 @@ var DshProviderManager = class {
|
|
|
1544
1571
|
}
|
|
1545
1572
|
/** Read the full `agent-default-model` selection (provider + model). */
|
|
1546
1573
|
async defaultModelSelection() {
|
|
1547
|
-
|
|
1548
|
-
const section = settings[AGENT_DEFAULT_MODEL_NAMESPACE];
|
|
1549
|
-
if (typeof section === "string") {
|
|
1550
|
-
return { provider: DEEPSEEK_PROVIDER, model: section };
|
|
1551
|
-
}
|
|
1552
|
-
if (isMapLike(section) && typeof section.model === "string") {
|
|
1553
|
-
return {
|
|
1554
|
-
provider: typeof section.provider === "string" ? section.provider : DEEPSEEK_PROVIDER,
|
|
1555
|
-
model: section.model
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
return void 0;
|
|
1574
|
+
return defaultModelSelectionFromSettings(await this.readSettings());
|
|
1559
1575
|
}
|
|
1560
1576
|
/**
|
|
1561
1577
|
* Resolve the provider that owns a model id across the configured providers.
|
|
@@ -7123,7 +7139,13 @@ function summaryText(state, locale) {
|
|
|
7123
7139
|
if (state.terminal === "interrupted") return zh ? "\u5DF2\u4E2D\u65AD" : "Interrupted";
|
|
7124
7140
|
if (state.terminal === "idle_timeout") return zh ? "\u5DF2\u8D85\u65F6" : "Timed out";
|
|
7125
7141
|
if (state.terminal === "error") return zh ? "\u51FA\u9519" : "Failed";
|
|
7126
|
-
if (state.terminal === "done")
|
|
7142
|
+
if (state.terminal === "done") {
|
|
7143
|
+
const hasToolWarning = state.blocks.some(
|
|
7144
|
+
(block) => block.kind === "tool" && block.tool.status === "error"
|
|
7145
|
+
);
|
|
7146
|
+
if (hasToolWarning) return zh ? "\u5DF2\u5B8C\u6210\uFF08\u542B\u8B66\u544A\uFF09" : "Completed with warnings";
|
|
7147
|
+
return zh ? "\u5DF2\u5B8C\u6210" : "Completed";
|
|
7148
|
+
}
|
|
7127
7149
|
if (state.footer === "tool_running") return zh ? "\u6B63\u5728\u8C03\u7528\u5DE5\u5177" : "Running tools";
|
|
7128
7150
|
if (state.footer === "streaming") return zh ? "\u6B63\u5728\u8F93\u51FA" : "Responding";
|
|
7129
7151
|
return zh ? "\u601D\u8003\u4E2D" : "Thinking";
|
|
@@ -7198,7 +7220,10 @@ function thinkingPanel(state, locale, maxTools) {
|
|
|
7198
7220
|
}
|
|
7199
7221
|
function compatibilityProcessSnapshot(state, locale, maxTools) {
|
|
7200
7222
|
const zh = locale === "zh_cn";
|
|
7201
|
-
const lines = [
|
|
7223
|
+
const lines = [
|
|
7224
|
+
zh ? "_\u6267\u884C\u72B6\u6001\uFF08\u517C\u5BB9\u663E\u793A\uFF09_" : "_Execution status (compatibility view)_",
|
|
7225
|
+
summaryText(state, locale)
|
|
7226
|
+
];
|
|
7202
7227
|
const toolBlocks = state.blocks.filter((block) => block.kind === "tool");
|
|
7203
7228
|
const visibleCount = Math.min(3, maxTools);
|
|
7204
7229
|
const tools = visibleCount === 0 ? [] : toolBlocks.slice(-visibleCount);
|
|
@@ -7209,7 +7234,6 @@ function compatibilityProcessSnapshot(state, locale, maxTools) {
|
|
|
7209
7234
|
for (const block of tools) {
|
|
7210
7235
|
lines.push(`\u{1F9F0} ${boundedText(block.tool.name, MAX_TOOL_NAME_LENGTH)} \xB7 ${block.tool.status}`);
|
|
7211
7236
|
}
|
|
7212
|
-
if (lines.length === 1) lines.push(state.terminal === "running" ? zh ? "\u6B63\u5728\u5904\u7406\u8BF7\u6C42\u2026" : "Processing the request\u2026" : zh ? "\u6267\u884C\u8FC7\u7A0B\u5DF2\u7ED3\u675F" : "Execution finished");
|
|
7213
7237
|
return noteMd(lines.join("\n"));
|
|
7214
7238
|
}
|
|
7215
7239
|
function runFailureLine(locale) {
|
|
@@ -7695,7 +7719,8 @@ function renderChannelContext(context) {
|
|
|
7695
7719
|
`language_policy: ui=${context.language.ui}; plain=${context.language.plain}; agent=${context.language.agent}`,
|
|
7696
7720
|
`secure_value_collection: ${context.secretCollection}`,
|
|
7697
7721
|
agentLanguage,
|
|
7698
|
-
"This turn arrived through the dsh-lark-bot Feishu/Lark channel. Use the dsh-lark-bot skill for channel setup, configuration, and diagnostics. Never ask for a secret in ordinary chat; use lark_request_secret when secure value collection is available."
|
|
7722
|
+
"This turn arrived through the dsh-lark-bot Feishu/Lark channel. Use the dsh-lark-bot skill for channel setup, configuration, and diagnostics. Never ask for a secret in ordinary chat; use lark_request_secret when secure value collection is available.",
|
|
7723
|
+
"Slash commands are handled by the bridge before the agent and are separate from model-callable tools: do not infer their absence from available_channel_tools. If the runtime skill cannot be loaded, tell the user to run /help for the authoritative command list."
|
|
7699
7724
|
].join("\n");
|
|
7700
7725
|
}
|
|
7701
7726
|
|
|
@@ -14183,6 +14208,22 @@ var NotifyServer = class {
|
|
|
14183
14208
|
respond(400, { ok: false, error: "sessionId and toolName are required" });
|
|
14184
14209
|
return;
|
|
14185
14210
|
}
|
|
14211
|
+
if (payload.policyCheckOnly) {
|
|
14212
|
+
const result2 = await this.deps.approval(payload);
|
|
14213
|
+
if (!result2.ok || !isPermissionPolicy(result2.policy)) {
|
|
14214
|
+
respond(404, {
|
|
14215
|
+
ok: false,
|
|
14216
|
+
...result2.error ? { error: result2.error } : { error: "invalid policy response" }
|
|
14217
|
+
});
|
|
14218
|
+
return;
|
|
14219
|
+
}
|
|
14220
|
+
respond(200, {
|
|
14221
|
+
ok: true,
|
|
14222
|
+
policy: result2.policy,
|
|
14223
|
+
...result2.denial === void 0 ? {} : { denial: result2.denial }
|
|
14224
|
+
});
|
|
14225
|
+
return;
|
|
14226
|
+
}
|
|
14186
14227
|
const result = await waitForHuman(
|
|
14187
14228
|
(signal) => this.deps.approval(payload, signal)
|
|
14188
14229
|
);
|
|
@@ -14270,6 +14311,9 @@ var NotifyServer = class {
|
|
|
14270
14311
|
}
|
|
14271
14312
|
}
|
|
14272
14313
|
};
|
|
14314
|
+
function isPermissionPolicy(value) {
|
|
14315
|
+
return value === "ask" || value === "allow" || value === "deny";
|
|
14316
|
+
}
|
|
14273
14317
|
function readBody(req) {
|
|
14274
14318
|
return new Promise((resolve6, reject) => {
|
|
14275
14319
|
const chunks = [];
|
|
@@ -20175,7 +20219,7 @@ async function runSupervise(options, deps = {}) {
|
|
|
20175
20219
|
mkdirSync(dirname17(logFile), { recursive: true });
|
|
20176
20220
|
const logFd = openSync(logFile, "a");
|
|
20177
20221
|
const writeStatus = async (state, childPid, restarts2) => {
|
|
20178
|
-
const processIdentity = await readLinuxProcessIdentity(process.pid);
|
|
20222
|
+
const processIdentity = await (deps.readProcessIdentity ?? readLinuxProcessIdentity)(process.pid);
|
|
20179
20223
|
const status = {
|
|
20180
20224
|
pid: process.pid,
|
|
20181
20225
|
childPid,
|
|
@@ -20211,8 +20255,9 @@ async function runSupervise(options, deps = {}) {
|
|
|
20211
20255
|
stdio: ["ignore", logFd, logFd]
|
|
20212
20256
|
});
|
|
20213
20257
|
child = spawned;
|
|
20258
|
+
const exited = waitForExit(spawned);
|
|
20214
20259
|
await writeStatus("running", spawned.pid, restarts);
|
|
20215
|
-
await
|
|
20260
|
+
await exited;
|
|
20216
20261
|
if (forceStopTimer) {
|
|
20217
20262
|
clearTimeout(forceStopTimer);
|
|
20218
20263
|
forceStopTimer = void 0;
|