dsh-lark-bot 0.9.0 → 0.9.1
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 +2 -1
- package/dist/ask.d.ts +25 -0
- package/dist/ask.js +95 -0
- package/dist/ask.js.map +1 -0
- package/dist/cli.js +8 -8
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +8 -8
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -592,6 +592,7 @@ pnpm install
|
|
|
592
592
|
pnpm typecheck
|
|
593
593
|
pnpm test
|
|
594
594
|
pnpm build
|
|
595
|
+
pnpm check:publish-bundle # 校验 dist 与全部 exports/bin 入口一致(发布前防线)| verifies dist matches every export & the CLI entry (release gate)
|
|
595
596
|
pnpm ci:local
|
|
596
597
|
pnpm release:check # ci:local + 上游一致性检查 | ci:local + upstream consistency check
|
|
597
598
|
pnpm compat:probe # 临时 DSH_HOME 安装锁定版 dsh,跑真实 SDK 握手 | installs pinned dsh into a temp DSH_HOME and runs a real SDK handshake
|
|
@@ -623,7 +624,7 @@ pnpm publish:dual:dry-run
|
|
|
623
624
|
pnpm publish:dual
|
|
624
625
|
```
|
|
625
626
|
|
|
626
|
-
`scripts/publish-dual-packages.mjs` 从根 `package.json` 生成两份仅 `name` / `bin`
|
|
627
|
+
`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
628
|
|
|
628
629
|
`scripts/publish-dual-packages.mjs` generates two publish manifests from the root
|
|
629
630
|
`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
|
@@ -246,14 +246,14 @@ function acpPluginInstalled(profileRoot) {
|
|
|
246
246
|
}
|
|
247
247
|
function isAcpProfileReady(profileRoot) {
|
|
248
248
|
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
|
|
249
|
+
return existsSync4(join4(profileRoot, "package.json")) && existsSync4(join4(profileRoot, "cordis.yml")) && existsSync4(join4(profileRoot, "cordis.patch.yml")) && acpPluginInstalled(profileRoot) && ownPackageLinked2(profileRoot, own);
|
|
250
250
|
}
|
|
251
|
-
function ownPackageLinked2(profileRoot,
|
|
252
|
-
const linkPath = join4(profileRoot, "node_modules",
|
|
251
|
+
function ownPackageLinked2(profileRoot, own) {
|
|
252
|
+
const linkPath = join4(profileRoot, "node_modules", own.name);
|
|
253
253
|
try {
|
|
254
254
|
const real = realpathSync2(linkPath);
|
|
255
255
|
const pkg = JSON.parse(readFileSync3(join4(real, "package.json"), "utf8"));
|
|
256
|
-
return pkg.name ===
|
|
256
|
+
return pkg.name === own.name && pkg.dsh?.bundle?.patch !== void 0 && real === realpathSync2(own.root);
|
|
257
257
|
} catch {
|
|
258
258
|
return false;
|
|
259
259
|
}
|
|
@@ -1352,14 +1352,14 @@ function sdkServerInstalled(profileRoot) {
|
|
|
1352
1352
|
}
|
|
1353
1353
|
function isSdkProfileReady(profileRoot) {
|
|
1354
1354
|
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
|
|
1355
|
+
return existsSync3(join3(profileRoot, "package.json")) && existsSync3(join3(profileRoot, "cordis.yml")) && existsSync3(join3(profileRoot, "cordis.patch.yml")) && sdkServerInstalled(profileRoot) && ownPackageLinked(profileRoot, own);
|
|
1356
1356
|
}
|
|
1357
|
-
function ownPackageLinked(profileRoot,
|
|
1358
|
-
const linkPath = join3(profileRoot, "node_modules",
|
|
1357
|
+
function ownPackageLinked(profileRoot, own) {
|
|
1358
|
+
const linkPath = join3(profileRoot, "node_modules", own.name);
|
|
1359
1359
|
try {
|
|
1360
1360
|
const real = realpathSync(linkPath);
|
|
1361
1361
|
const pkg = JSON.parse(readFileSync2(join3(real, "package.json"), "utf8"));
|
|
1362
|
-
return pkg.name ===
|
|
1362
|
+
return pkg.name === own.name && pkg.dsh?.bundle?.patch !== void 0 && real === realpathSync(own.root);
|
|
1363
1363
|
} catch {
|
|
1364
1364
|
return false;
|
|
1365
1365
|
}
|