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 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` 不同的发布清单,避免两份源码漂移。GitHub tag `v*` 会触发 [`release.yml`](.github/workflows/release.yml) 自动发布两个 npm 包并创建 Release。
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
@@ -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.name);
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, ownName) {
252
- const linkPath = join4(profileRoot, "node_modules", ownName);
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 === ownName && pkg.dsh?.bundle?.patch !== void 0;
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.name);
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, ownName) {
1358
- const linkPath = join3(profileRoot, "node_modules", ownName);
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 === ownName && pkg.dsh?.bundle?.patch !== void 0;
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
  }