dsh-lark-bot 0.9.0 → 0.9.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/dist/ask.d.ts +25 -0
- package/dist/ask.js +95 -0
- package/dist/ask.js.map +1 -0
- package/dist/cli.js +12 -10
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +11 -9
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,12 +87,14 @@ npx dsh-lark-bot@latest setup --profile dsh-lark
|
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
`setup` 会自动完成:发现本机 dsh → 预批准 pnpm 构建策略 → 执行标准的
|
|
90
|
-
`dsh plugin --profile dsh-lark add dsh-lark-bot
|
|
90
|
+
`dsh plugin --profile dsh-lark add dsh-lark-bot@<版本>`(版本号由当前包固定,避免 pnpm
|
|
91
|
+
裸名解析到旧版本),并**默认同时安装「安全网守护」**——系统级
|
|
91
92
|
常驻、dsh 全部下线后仍保留飞书救援入口(核心能力之一,见下文「安全网守护」一节)。
|
|
92
93
|
一条命令即完成全部安装。
|
|
93
94
|
|
|
94
95
|
`setup` automatically: locates your dsh install → pre-approves pnpm's build policy → runs the
|
|
95
|
-
standard `dsh plugin --profile dsh-lark add dsh-lark-bot
|
|
96
|
+
standard `dsh plugin --profile dsh-lark add dsh-lark-bot@<version>` (pinned to the running
|
|
97
|
+
package so pnpm never resolves an outdated bare-name release), and **also installs the safety-net
|
|
96
98
|
guardian by default** — a system-level resident process that keeps the Feishu rescue entrance
|
|
97
99
|
alive even when dsh is fully down (one of the core features; see "Safety-net guardian" below).
|
|
98
100
|
One command installs everything.
|
|
@@ -592,6 +594,7 @@ pnpm install
|
|
|
592
594
|
pnpm typecheck
|
|
593
595
|
pnpm test
|
|
594
596
|
pnpm build
|
|
597
|
+
pnpm check:publish-bundle # 校验 dist 与全部 exports/bin 入口一致(发布前防线)| verifies dist matches every export & the CLI entry (release gate)
|
|
595
598
|
pnpm ci:local
|
|
596
599
|
pnpm release:check # ci:local + 上游一致性检查 | ci:local + upstream consistency check
|
|
597
600
|
pnpm compat:probe # 临时 DSH_HOME 安装锁定版 dsh,跑真实 SDK 握手 | installs pinned dsh into a temp DSH_HOME and runs a real SDK handshake
|
|
@@ -623,7 +626,7 @@ pnpm publish:dual:dry-run
|
|
|
623
626
|
pnpm publish:dual
|
|
624
627
|
```
|
|
625
628
|
|
|
626
|
-
`scripts/publish-dual-packages.mjs` 从根 `package.json` 生成两份仅 `name` / `bin`
|
|
629
|
+
`scripts/publish-dual-packages.mjs` 从根 `package.json` 生成两份仅 `name` / `bin` 不同的发布清单,避免两份源码漂移。发布时整目录同步 `dist/`,并在发布前校验 `package.json` 每个 `exports` 子路径与 CLI 入口在产物中都存在——任何缺失(如 v0.9.0 的 `ask` 入口漏拷)都会直接中止发布。GitHub tag `v*` 会触发 [`release.yml`](.github/workflows/release.yml) 自动发布两个 npm 包并创建 Release。
|
|
627
630
|
|
|
628
631
|
`scripts/publish-dual-packages.mjs` generates two publish manifests from the root
|
|
629
632
|
`package.json`, differing only in `name` / `bin`, so the two copies never drift. A GitHub tag
|
package/dist/ask.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
|
|
3
|
+
/** Cordis plugin name; referenced by the runtime patch row. */
|
|
4
|
+
declare const name = "lark-ask";
|
|
5
|
+
/** Requires the dsh tool registry before registering. */
|
|
6
|
+
declare const inject: string[];
|
|
7
|
+
interface Config {
|
|
8
|
+
/** Localhost callback URL of the running bridge process (/ask). */
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
/** Shared token authorizing the callback. */
|
|
11
|
+
token?: string;
|
|
12
|
+
}
|
|
13
|
+
/** The user may take a while to answer a card; keep the tool well above the
|
|
14
|
+
* default run-timeout so the question itself is not what kills the task. */
|
|
15
|
+
declare const ASK_TOOL_TIMEOUT_MS = 600000;
|
|
16
|
+
/**
|
|
17
|
+
* dsh tool that asks the user a question through a Feishu/Lark card when the
|
|
18
|
+
* agent needs a decision, confirmation, or missing information before
|
|
19
|
+
* proceeding. The bridge runs a localhost-only callback server; this plugin
|
|
20
|
+
* is the runtime side of that channel: it posts the question and blocks until
|
|
21
|
+
* the human answers the card.
|
|
22
|
+
*/
|
|
23
|
+
declare function apply(ctx: Context, config?: Config): void;
|
|
24
|
+
|
|
25
|
+
export { ASK_TOOL_TIMEOUT_MS, type Config, apply, inject, name };
|
package/dist/ask.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/notify/ask-tool.ts
|
|
2
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
var name = "lark-ask";
|
|
4
|
+
var inject = ["tools"];
|
|
5
|
+
var ASK_TOOL_TIMEOUT_MS = 6e5;
|
|
6
|
+
function apply(ctx, config = {}) {
|
|
7
|
+
ctx.tools.register(
|
|
8
|
+
defineTool({
|
|
9
|
+
name: "lark_ask_user",
|
|
10
|
+
description: "Ask the user a question through a Feishu/Lark card when you need a decision, confirmation, or missing information before proceeding. The tool blocks until the user answers. Use it sparingly and only for choices or facts only the user can provide; resolve everything discoverable by inspection yourself first.",
|
|
11
|
+
timeoutMs: ASK_TOOL_TIMEOUT_MS,
|
|
12
|
+
parameters: {
|
|
13
|
+
question: {
|
|
14
|
+
type: "string",
|
|
15
|
+
required: true,
|
|
16
|
+
description: "The question to ask the user."
|
|
17
|
+
},
|
|
18
|
+
kind: {
|
|
19
|
+
type: "string",
|
|
20
|
+
enum: ["single", "multi", "text"],
|
|
21
|
+
description: "single = one choice, multi = multiple choices, text = free text. Defaults to single when options are given, otherwise text."
|
|
22
|
+
},
|
|
23
|
+
options: {
|
|
24
|
+
type: "array",
|
|
25
|
+
items: { type: "string" },
|
|
26
|
+
description: "Choices for single / multi questions."
|
|
27
|
+
},
|
|
28
|
+
header: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Optional short heading shown above the question."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
output: {
|
|
34
|
+
schema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
properties: {
|
|
38
|
+
answered: { type: "boolean", required: true },
|
|
39
|
+
answer: { type: "json" },
|
|
40
|
+
error: { type: "string" }
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
render: (_args, value) => [
|
|
44
|
+
{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: value.answered ? `User answered: ${JSON.stringify(value.answer)}` : `Question failed: ${value.error ?? "no answer"}`
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
async execute(args, exec) {
|
|
51
|
+
const endpoint = config.endpoint ?? process.env.DSH_LARK_ASK_URL;
|
|
52
|
+
const token = config.token ?? process.env.DSH_LARK_NOTIFY_TOKEN;
|
|
53
|
+
if (!endpoint || !token) {
|
|
54
|
+
throw new Error("lark_ask_user is not configured (endpoint/token missing)");
|
|
55
|
+
}
|
|
56
|
+
const sessionId = exec?.agent?.session === void 0 ? void 0 : String(exec.agent.session.id);
|
|
57
|
+
if (!sessionId) {
|
|
58
|
+
throw new Error("lark_ask_user needs an active session to route the question");
|
|
59
|
+
}
|
|
60
|
+
const kind = args.kind ?? (args.options && args.options.length > 0 ? "single" : "text");
|
|
61
|
+
const response = await fetch(endpoint, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "content-type": "application/json" },
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
token,
|
|
66
|
+
sessionId,
|
|
67
|
+
question: args.question,
|
|
68
|
+
kind,
|
|
69
|
+
...args.options && args.options.length > 0 ? { options: args.options } : {},
|
|
70
|
+
...args.header === void 0 ? {} : { header: args.header }
|
|
71
|
+
}),
|
|
72
|
+
...exec?.signal === void 0 ? {} : { signal: exec.signal }
|
|
73
|
+
});
|
|
74
|
+
const body = await response.json();
|
|
75
|
+
if (!response.ok || body.ok !== true) {
|
|
76
|
+
return {
|
|
77
|
+
answered: false,
|
|
78
|
+
...body.error === void 0 ? {} : { error: body.error }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
answered: true,
|
|
83
|
+
...body.answer === void 0 ? {} : { answer: body.answer ?? null }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
ASK_TOOL_TIMEOUT_MS,
|
|
91
|
+
apply,
|
|
92
|
+
inject,
|
|
93
|
+
name
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=ask.js.map
|
package/dist/ask.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/notify/ask-tool.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport { defineTool } from '@deepseek-ai/dsh-tools';\n\n/** Cordis plugin name; referenced by the runtime patch row. */\nexport const name = 'lark-ask';\n\n/** Requires the dsh tool registry before registering. */\nexport const inject = ['tools'];\n\nexport interface Config {\n /** Localhost callback URL of the running bridge process (/ask). */\n endpoint?: string;\n /** Shared token authorizing the callback. */\n token?: string;\n}\n\n/** The user may take a while to answer a card; keep the tool well above the\n * default run-timeout so the question itself is not what kills the task. */\nexport const ASK_TOOL_TIMEOUT_MS = 600_000;\n\ninterface AskToolExec {\n agent?: { session?: { id?: unknown } };\n signal?: AbortSignal;\n}\n\n/**\n * dsh tool that asks the user a question through a Feishu/Lark card when the\n * agent needs a decision, confirmation, or missing information before\n * proceeding. The bridge runs a localhost-only callback server; this plugin\n * is the runtime side of that channel: it posts the question and blocks until\n * the human answers the card.\n */\nexport function apply(ctx: Context, config: Config = {}) {\n ctx.tools.register(\n defineTool({\n name: 'lark_ask_user',\n description:\n 'Ask the user a question through a Feishu/Lark card when you need a decision, confirmation, or missing information before proceeding. The tool blocks until the user answers. Use it sparingly and only for choices or facts only the user can provide; resolve everything discoverable by inspection yourself first.',\n timeoutMs: ASK_TOOL_TIMEOUT_MS,\n parameters: {\n question: {\n type: 'string',\n required: true,\n description: 'The question to ask the user.',\n },\n kind: {\n type: 'string',\n enum: ['single', 'multi', 'text'],\n description:\n 'single = one choice, multi = multiple choices, text = free text. Defaults to single when options are given, otherwise text.',\n },\n options: {\n type: 'array',\n items: { type: 'string' },\n description: 'Choices for single / multi questions.',\n },\n header: {\n type: 'string',\n description: 'Optional short heading shown above the question.',\n },\n },\n output: {\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n answered: { type: 'boolean', required: true },\n answer: { type: 'json' },\n error: { type: 'string' },\n },\n },\n render: (_args, value) => [\n {\n type: 'text',\n text: value.answered\n ? `User answered: ${JSON.stringify(value.answer)}`\n : `Question failed: ${value.error ?? 'no answer'}`,\n },\n ],\n },\n async execute(args, exec: AskToolExec | undefined) {\n // The callback endpoint/token may be configured by the patch row or\n // injected at runtime: the bridge process sets the env vars when its\n // notify server starts, so read them lazily at execute time.\n const endpoint = config.endpoint ?? process.env.DSH_LARK_ASK_URL;\n const token = config.token ?? process.env.DSH_LARK_NOTIFY_TOKEN;\n if (!endpoint || !token) {\n throw new Error('lark_ask_user is not configured (endpoint/token missing)');\n }\n const sessionId =\n exec?.agent?.session === undefined\n ? undefined\n : String(exec.agent.session.id);\n if (!sessionId) {\n throw new Error('lark_ask_user needs an active session to route the question');\n }\n const kind =\n args.kind ?? (args.options && args.options.length > 0 ? 'single' : 'text');\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n token,\n sessionId,\n question: args.question,\n kind,\n ...(args.options && args.options.length > 0 ? { options: args.options } : {}),\n ...(args.header === undefined ? {} : { header: args.header }),\n }),\n ...(exec?.signal === undefined ? {} : { signal: exec.signal }),\n });\n const body = (await response.json()) as {\n ok?: boolean;\n answer?: string | string[] | null;\n error?: string;\n };\n if (!response.ok || body.ok !== true) {\n return {\n answered: false,\n ...(body.error === undefined ? {} : { error: body.error }),\n };\n }\n return {\n answered: true,\n ...(body.answer === undefined ? {} : { answer: body.answer ?? null }),\n };\n },\n }),\n );\n}\n"],"mappings":";AACA,SAAS,kBAAkB;AAGpB,IAAM,OAAO;AAGb,IAAM,SAAS,CAAC,OAAO;AAWvB,IAAM,sBAAsB;AAc5B,SAAS,MAAM,KAAc,SAAiB,CAAC,GAAG;AACvD,MAAI,MAAM;AAAA,IACR,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aACE;AAAA,MACF,WAAW;AAAA,MACX,YAAY;AAAA,QACV,UAAU;AAAA,UACR,MAAM;AAAA,UACN,UAAU;AAAA,UACV,aAAa;AAAA,QACf;AAAA,QACA,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,MAAM,CAAC,UAAU,SAAS,MAAM;AAAA,UAChC,aACE;AAAA,QACJ;AAAA,QACA,SAAS;AAAA,UACP,MAAM;AAAA,UACN,OAAO,EAAE,MAAM,SAAS;AAAA,UACxB,aAAa;AAAA,QACf;AAAA,QACA,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,sBAAsB;AAAA,UACtB,YAAY;AAAA,YACV,UAAU,EAAE,MAAM,WAAW,UAAU,KAAK;AAAA,YAC5C,QAAQ,EAAE,MAAM,OAAO;AAAA,YACvB,OAAO,EAAE,MAAM,SAAS;AAAA,UAC1B;AAAA,QACF;AAAA,QACA,QAAQ,CAAC,OAAO,UAAU;AAAA,UACxB;AAAA,YACE,MAAM;AAAA,YACN,MAAM,MAAM,WACR,kBAAkB,KAAK,UAAU,MAAM,MAAM,CAAC,KAC9C,oBAAoB,MAAM,SAAS,WAAW;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAAA,MACA,MAAM,QAAQ,MAAM,MAA+B;AAIjD,cAAM,WAAW,OAAO,YAAY,QAAQ,IAAI;AAChD,cAAM,QAAQ,OAAO,SAAS,QAAQ,IAAI;AAC1C,YAAI,CAAC,YAAY,CAAC,OAAO;AACvB,gBAAM,IAAI,MAAM,0DAA0D;AAAA,QAC5E;AACA,cAAM,YACJ,MAAM,OAAO,YAAY,SACrB,SACA,OAAO,KAAK,MAAM,QAAQ,EAAE;AAClC,YAAI,CAAC,WAAW;AACd,gBAAM,IAAI,MAAM,6DAA6D;AAAA,QAC/E;AACA,cAAM,OACJ,KAAK,SAAS,KAAK,WAAW,KAAK,QAAQ,SAAS,IAAI,WAAW;AACrE,cAAM,WAAW,MAAM,MAAM,UAAU;AAAA,UACrC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB;AAAA,YACA;AAAA,YACA,UAAU,KAAK;AAAA,YACf;AAAA,YACA,GAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,IAAI,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,YAC3E,GAAI,KAAK,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO;AAAA,UAC7D,CAAC;AAAA,UACD,GAAI,MAAM,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO;AAAA,QAC9D,CAAC;AACD,cAAM,OAAQ,MAAM,SAAS,KAAK;AAKlC,YAAI,CAAC,SAAS,MAAM,KAAK,OAAO,MAAM;AACpC,iBAAO;AAAA,YACL,UAAU;AAAA,YACV,GAAI,KAAK,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM;AAAA,UAC1D;AAAA,QACF;AACA,eAAO;AAAA,UACL,UAAU;AAAA,UACV,GAAI,KAAK,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,KAAK,UAAU,KAAK;AAAA,QACrE;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -145,7 +145,9 @@ function findOwnPackageRoot(startDir) {
|
|
|
145
145
|
try {
|
|
146
146
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
147
147
|
if (typeof pkg.name === "string" && isOwnPackageName(pkg.name)) {
|
|
148
|
-
|
|
148
|
+
const info = { name: pkg.name, root: dir };
|
|
149
|
+
if (typeof pkg.version === "string") info.version = pkg.version;
|
|
150
|
+
return info;
|
|
149
151
|
}
|
|
150
152
|
} catch {
|
|
151
153
|
}
|
|
@@ -246,14 +248,14 @@ function acpPluginInstalled(profileRoot) {
|
|
|
246
248
|
}
|
|
247
249
|
function isAcpProfileReady(profileRoot) {
|
|
248
250
|
const own = ownPackageInfo();
|
|
249
|
-
return existsSync4(join4(profileRoot, "package.json")) && existsSync4(join4(profileRoot, "cordis.yml")) && existsSync4(join4(profileRoot, "cordis.patch.yml")) && acpPluginInstalled(profileRoot) && ownPackageLinked2(profileRoot, own
|
|
251
|
+
return existsSync4(join4(profileRoot, "package.json")) && existsSync4(join4(profileRoot, "cordis.yml")) && existsSync4(join4(profileRoot, "cordis.patch.yml")) && acpPluginInstalled(profileRoot) && ownPackageLinked2(profileRoot, own);
|
|
250
252
|
}
|
|
251
|
-
function ownPackageLinked2(profileRoot,
|
|
252
|
-
const linkPath = join4(profileRoot, "node_modules",
|
|
253
|
+
function ownPackageLinked2(profileRoot, own) {
|
|
254
|
+
const linkPath = join4(profileRoot, "node_modules", own.name);
|
|
253
255
|
try {
|
|
254
256
|
const real = realpathSync2(linkPath);
|
|
255
257
|
const pkg = JSON.parse(readFileSync3(join4(real, "package.json"), "utf8"));
|
|
256
|
-
return pkg.name ===
|
|
258
|
+
return pkg.name === own.name && pkg.dsh?.bundle?.patch !== void 0 && real === realpathSync2(own.root);
|
|
257
259
|
} catch {
|
|
258
260
|
return false;
|
|
259
261
|
}
|
|
@@ -1352,14 +1354,14 @@ function sdkServerInstalled(profileRoot) {
|
|
|
1352
1354
|
}
|
|
1353
1355
|
function isSdkProfileReady(profileRoot) {
|
|
1354
1356
|
const own = ownPackageInfo();
|
|
1355
|
-
return existsSync3(join3(profileRoot, "package.json")) && existsSync3(join3(profileRoot, "cordis.yml")) && existsSync3(join3(profileRoot, "cordis.patch.yml")) && sdkServerInstalled(profileRoot) && ownPackageLinked(profileRoot, own
|
|
1357
|
+
return existsSync3(join3(profileRoot, "package.json")) && existsSync3(join3(profileRoot, "cordis.yml")) && existsSync3(join3(profileRoot, "cordis.patch.yml")) && sdkServerInstalled(profileRoot) && ownPackageLinked(profileRoot, own);
|
|
1356
1358
|
}
|
|
1357
|
-
function ownPackageLinked(profileRoot,
|
|
1358
|
-
const linkPath = join3(profileRoot, "node_modules",
|
|
1359
|
+
function ownPackageLinked(profileRoot, own) {
|
|
1360
|
+
const linkPath = join3(profileRoot, "node_modules", own.name);
|
|
1359
1361
|
try {
|
|
1360
1362
|
const real = realpathSync(linkPath);
|
|
1361
1363
|
const pkg = JSON.parse(readFileSync2(join3(real, "package.json"), "utf8"));
|
|
1362
|
-
return pkg.name ===
|
|
1364
|
+
return pkg.name === own.name && pkg.dsh?.bundle?.patch !== void 0 && real === realpathSync(own.root);
|
|
1363
1365
|
} catch {
|
|
1364
1366
|
return false;
|
|
1365
1367
|
}
|
|
@@ -6281,7 +6283,7 @@ async function uninstallGuardian(options) {
|
|
|
6281
6283
|
async function runSetup(options = {}) {
|
|
6282
6284
|
const profile = options.profile ?? "dsh-lark";
|
|
6283
6285
|
const own = ownPackageInfo();
|
|
6284
|
-
const packageSpec = options.packageSpec ?? process.env.DSH_LARK_SETUP_PACKAGE ?? own.name;
|
|
6286
|
+
const packageSpec = options.packageSpec ?? process.env.DSH_LARK_SETUP_PACKAGE ?? (own.version ? `${own.name}@${own.version}` : own.name);
|
|
6285
6287
|
const dshHome = options.dshHome ?? resolveDshHome(homedir7(), process.env);
|
|
6286
6288
|
const bin = options.bin ?? discoverDshBin(homedir7(), process.env);
|
|
6287
6289
|
if (!bin) {
|