dsh-lark-agent 0.3.1 → 0.3.3
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 +1 -3
- package/dist/bot-menu.d.ts +12 -0
- package/dist/bot-menu.js +14 -0
- package/dist/bridge.js +4 -2
- package/dist/client.cjs +23 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.js +2 -2
- package/dist/live-reply.js +8 -5
- package/dist/onboarding/controller.d.ts +2 -0
- package/dist/onboarding/controller.js +7 -1
- package/dist/onboarding/host.d.ts +3 -2
- package/dist/onboarding/host.js +2 -2
- package/dist/onboarding/lark-platform.d.ts +2 -0
- package/dist/onboarding/lark-platform.js +5 -0
- package/dist/onboarding/protocol.d.ts +9 -0
- package/dist/onboarding/updates.d.ts +13 -3
- package/dist/onboarding/updates.js +24 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,8 +30,6 @@ Before you start:
|
|
|
30
30
|
|
|
31
31
|
In the sidebar, open **插件 → 添加插件** (Plugins → Add plugin), enter `dsh-lark-agent`, click **安装** (Install), then **立即启用** (Enable now). Restart if the app asks.
|
|
32
32
|
|
|
33
|
-
<img src="https://raw.githubusercontent.com/Nirvana-Jie/dsh-lark-agent/main/assets/readme/desktop-install-02.jpg" width="720" alt="The Add plugin dialog in the DeepSeek Harness desktop app">
|
|
34
|
-
|
|
35
33
|
### Install with the `dsh` CLI
|
|
36
34
|
|
|
37
35
|
[`dsh plugin`](https://www.npmjs.com/package/@deepseek-ai/dsh) runs pnpm, so pnpm must be on your PATH:
|
|
@@ -53,7 +51,7 @@ Then restart `dsh web`.
|
|
|
53
51
|
|
|
54
52
|
### Update
|
|
55
53
|
|
|
56
|
-
When a new version
|
|
54
|
+
When a new version has been out for a day, the Feishu settings page shows a notice with these steps (in the desktop app it also opens the plugin list for you). Until then the desktop app and `dsh plugin` still install the previous version, because pnpm 11 skips releases less than 24 hours old. If an install picks an older version that your DeepSeek Harness rejects as incompatible, see [Troubleshooting](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/DEVELOPMENT.md#排障).
|
|
57
55
|
|
|
58
56
|
1. On the Feishu settings page, click **暂停连接** (pause) and back up as described in [Upgrade](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/DEVELOPMENT.md#升级).
|
|
59
57
|
2. Desktop app: the Add plugin dialog says third-party plugins must be uninstalled before a new version is installed, so uninstall `dsh-lark-agent` on the Plugins page and add it again (this in-app upgrade hasn't been tested end to end yet; an earlier source build is listed as `dsh-larkbot`). CLI: run `dsh plugin --profile web update dsh-lark-agent@latest`, then restart `dsh web`.
|
package/dist/bot-menu.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export declare const BOT_MENU_COMMANDS: Readonly<{
|
|
|
10
10
|
}>;
|
|
11
11
|
export type BotMenuCommand = (typeof BOT_MENU_COMMANDS)[keyof typeof BOT_MENU_COMMANDS];
|
|
12
12
|
export declare function botMenuCommand(eventKey: unknown): BotMenuCommand | undefined;
|
|
13
|
+
/** A menu entry as the owner adds it in the developer console. */
|
|
14
|
+
export interface BotMenuEntry {
|
|
15
|
+
label: string;
|
|
16
|
+
eventKey: string;
|
|
17
|
+
parent?: string;
|
|
18
|
+
}
|
|
13
19
|
export interface BotMenuNode {
|
|
14
20
|
menu_id: string;
|
|
15
21
|
parent_menu_id?: string;
|
|
@@ -32,6 +38,12 @@ export declare function buildBotMenuAbility(): {
|
|
|
32
38
|
bot_menus: BotMenuNode[];
|
|
33
39
|
};
|
|
34
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* The entries a published menu lacks, by label. A menu configured by hand
|
|
43
|
+
* before a release added an entry stays without it; none is reported when
|
|
44
|
+
* no dsh entry was configured at all.
|
|
45
|
+
*/
|
|
46
|
+
export declare function missingBotMenuEntries(eventKeys: readonly string[]): BotMenuEntry[];
|
|
35
47
|
/** Pure review/export plan: this module never invokes an API or changes an app. */
|
|
36
48
|
export declare function buildBotMenuPlan(appId: string): {
|
|
37
49
|
scope: string;
|
package/dist/bot-menu.js
CHANGED
|
@@ -29,6 +29,20 @@ export function buildBotMenuAbility() {
|
|
|
29
29
|
{ menu_id: 'dsh_permission', parent_menu_id: 'dsh_settings', sort: 2, default_name: '权限模式', menu_content_type: 2, event_key: 'dsh.permission' },
|
|
30
30
|
] } };
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The entries a published menu lacks, by label. A menu configured by hand
|
|
34
|
+
* before a release added an entry stays without it; none is reported when
|
|
35
|
+
* no dsh entry was configured at all.
|
|
36
|
+
*/
|
|
37
|
+
export function missingBotMenuEntries(eventKeys) {
|
|
38
|
+
const nodes = buildBotMenuAbility().bot.bot_menus, entries = nodes.filter(node => node.event_key);
|
|
39
|
+
if (!entries.some(node => eventKeys.includes(node.event_key)))
|
|
40
|
+
return [];
|
|
41
|
+
return entries.filter(node => !eventKeys.includes(node.event_key)).map(node => {
|
|
42
|
+
const parent = nodes.find(candidate => candidate.menu_id === node.parent_menu_id)?.default_name;
|
|
43
|
+
return { label: node.default_name, eventKey: node.event_key, ...(parent ? { parent } : {}) };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
32
46
|
/** Pure review/export plan: this module never invokes an API or changes an app. */
|
|
33
47
|
export function buildBotMenuPlan(appId) {
|
|
34
48
|
if (typeof appId !== 'string' || !/^cli_[A-Za-z0-9_-]{1,128}$/.test(appId))
|
package/dist/bridge.js
CHANGED
|
@@ -433,9 +433,11 @@ export class BotBridge {
|
|
|
433
433
|
return;
|
|
434
434
|
const controller = new AbortController();
|
|
435
435
|
lane.controller = controller;
|
|
436
|
-
let timedOut = false;
|
|
437
|
-
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, this.config.turnTimeoutMs);
|
|
438
436
|
const harness = message.chatType === 'p2p' && lane.user.directMode === 'harness';
|
|
437
|
+
let timedOut = false;
|
|
438
|
+
// Like the App, a turn runs until it ends or someone sends /stop. Only a configured cap bounds a restricted turn.
|
|
439
|
+
const deadline = harness ? undefined : this.config.turnTimeoutMs;
|
|
440
|
+
const timer = deadline === undefined ? undefined : setTimeout(() => { timedOut = true; controller.abort(); }, deadline);
|
|
439
441
|
let live;
|
|
440
442
|
// Once a live card exists it carries this turn's whole outcome; nothing else is sent as text.
|
|
441
443
|
const conclude = async (final) => {
|
package/dist/client.cjs
CHANGED
|
@@ -111,6 +111,15 @@ function parseSnapshot(value) {
|
|
|
111
111
|
snapshot.documentSetup.qr = { url: officialUrl(qr.url), image, expiresAt: timestamp(qr.expiresAt) };
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
if (data.menu !== void 0) {
|
|
115
|
+
const menu = object(data.menu);
|
|
116
|
+
if (!Array.isArray(menu.missing) || !menu.missing.length || menu.missing.length > 10) throw new Error("invalid menu");
|
|
117
|
+
snapshot.menu = { url: officialUrl(menu.url), missing: menu.missing.map((value2) => {
|
|
118
|
+
const entry = object(value2);
|
|
119
|
+
if (typeof entry.eventKey !== "string" || !/^dsh\.[a-z]{1,30}$/.test(entry.eventKey)) throw new Error("invalid menu");
|
|
120
|
+
return { label: text(entry.label, 60), eventKey: entry.eventKey, ...entry.parent === void 0 ? {} : { parent: text(entry.parent, 60) } };
|
|
121
|
+
}) };
|
|
122
|
+
}
|
|
114
123
|
if (data.update !== void 0) {
|
|
115
124
|
const update = object(data.update);
|
|
116
125
|
const version = (value2) => {
|
|
@@ -330,6 +339,18 @@ function Steps({ active }) {
|
|
|
330
339
|
label
|
|
331
340
|
] }, label)) });
|
|
332
341
|
}
|
|
342
|
+
function MenuNotice({ menu, phase }) {
|
|
343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-update", role: "status", children: [
|
|
344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u98DE\u4E66\u5E95\u90E8\u83DC\u5355\u7F3A\u5C11\u5165\u53E3" }),
|
|
345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: menu.missing.map((entry) => `${entry.parent ? `${entry.parent} \u2192 ` : ""}${entry.label}\uFF08\u4E8B\u4EF6\u6807\u8BC6 ${entry.eventKey}\uFF09`).join("\u3001") }),
|
|
346
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ol", { className: "lark-instructions", children: [
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "\u5728\u98DE\u4E66\u5F00\u53D1\u8005\u540E\u53F0\u6253\u5F00\u300C\u673A\u5668\u4EBA\u81EA\u5B9A\u4E49\u83DC\u5355\u300D\u8865\u4E0A\uFF0C\u54CD\u5E94\u52A8\u4F5C\u9009\u300C\u63A8\u9001\u4E8B\u4EF6\u300D\uFF0C\u4E8B\u4EF6\u6807\u8BC6\u9010\u5B57\u586B\u5199\u3002" }),
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "\u521B\u5EFA\u7248\u672C\u5E76\u53D1\u5E03\uFF0C\u5BA2\u6237\u7AEF\u7EA6 5 \u5206\u949F\u540E\u66F4\u65B0\u3002" }),
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: phase === "paused" ? "\u56DE\u5230\u8FD9\u91CC\u70B9\u300C\u6062\u590D\u8FDE\u63A5\u300D\u3002" : "\u56DE\u5230\u8FD9\u91CC\u70B9\u300C\u6682\u505C\u8FDE\u63A5\u300D\uFF0C\u518D\u70B9\u300C\u6062\u590D\u8FDE\u63A5\u300D\u3002" })
|
|
350
|
+
] }),
|
|
351
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lark-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(External, { url: menu.url, children: "\u6253\u5F00\u98DE\u4E66\u540E\u53F0" }) })
|
|
352
|
+
] });
|
|
353
|
+
}
|
|
333
354
|
function UpdateNotice({ update, host, phase }) {
|
|
334
355
|
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
335
356
|
const [status, setStatus] = (0, import_react2.useState)("");
|
|
@@ -594,6 +615,7 @@ function ConnectionCard({ api, host }) {
|
|
|
594
615
|
] }),
|
|
595
616
|
!snapshot.capabilities.documents && !snapshot.documentSetup && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", disabled, onClick: () => void mutate("enable-documents"), children: "\u8865\u5145\u9ED8\u8BA4\u6743\u9650" })
|
|
596
617
|
] }),
|
|
618
|
+
snapshot.menu && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuNotice, { menu: snapshot.menu, phase }),
|
|
597
619
|
snapshot.documentSetup && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-notice", role: "status", children: [
|
|
598
620
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: snapshot.documentSetup.message }),
|
|
599
621
|
snapshot.documentSetup.phase === "preparing" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "lark-spinner", "aria-label": "\u6B63\u5728\u51C6\u5907\u6388\u6743" }),
|
|
@@ -671,7 +693,7 @@ function ConnectionCard({ api, host }) {
|
|
|
671
693
|
// src/client/styles.ts
|
|
672
694
|
var css = `
|
|
673
695
|
.dsh-lark-settings{--lark-ink:#263247;--lark-muted:#738096;--lark-line:#e4eaf2;--lark-blue:#4268df;--lark-surface:#fff;--lark-soft:#f5f8fd;font:14px/1.65 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;color:var(--lark-ink);max-width:720px;margin:0 auto;padding:8px 0 20px;box-sizing:border-box}
|
|
674
|
-
.dsh-lark-settings *{box-sizing:border-box}.dsh-lark-settings button,.dsh-lark-settings input{font:inherit}.dsh-lark-settings button,.dsh-lark-settings summary{cursor:pointer}.dsh-lark-settings button:disabled{opacity:.5;cursor:not-allowed}.dsh-lark-settings button:focus-visible,.dsh-lark-settings a:focus-visible,.dsh-lark-settings input:focus-visible,.dsh-lark-settings summary:focus-visible{outline:3px solid #a1b8ff;outline-offset:3px}.dsh-lark-settings a{overflow-wrap:anywhere}.lark-heading{margin:0 0 22px}.lark-heading h2{font-size:23px;font-weight:650;margin:0 0 5px;letter-spacing:-.4px}.lark-heading p,.lark-card-header p{color:var(--lark-muted);font-size:13px;margin:0}.lark-card{border:1px solid var(--lark-line);border-radius:15px;background:var(--lark-surface);overflow:hidden;box-shadow:0 2px 5px #27385504}.lark-card-header{display:flex;align-items:center;gap:12px;padding:19px 23px;border-bottom:1px solid var(--lark-line)}.lark-plugin-icon{width:43px;height:43px;object-fit:contain;flex-shrink:0}.lark-card-header h2{font-size:15px;font-weight:650;line-height:1.6;margin:0}.lark-card-header p{font-size:12px}.lark-status{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--lark-muted);white-space:nowrap}.lark-status i{width:6px;height:6px;border-radius:50%;background:#9da9bc}.lark-status.online{color:#248470}.lark-status.online i{background:#248470}.lark-body{padding:28px;position:relative}.lark-body h3{font-size:20px;font-weight:650;letter-spacing:-.25px;margin:0 0 9px;line-height:1.5}.lark-copy{color:var(--lark-muted);font-size:13px;margin:0 0 20px}.lark-centered{text-align:center;line-height:1.9}.lark-body .lark-intro-title{text-align:center;font-size:21px}.lark-brands{display:flex;justify-content:center;align-items:center;gap:16px;margin:5px 0 25px}.lark-brand{width:53px;height:53px;display:grid;place-items:center;border-radius:14px;background:var(--lark-soft);border:1px solid var(--lark-line)}.lark-brand img{width:37px;height:37px;object-fit:contain}.lark-brand-line{width:78px;height:1px;background:#dbe3ee;position:relative}.lark-brand-line:after{content:"";position:absolute;left:calc(50% - 3px);top:-3px;width:7px;height:7px;border-radius:50%;background:#a9bbdc;border:2px solid var(--lark-surface)}.lark-brands-connected .lark-brand-line{background:#99d1c1}.lark-brands-connected .lark-brand-line:after{background:#248470}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:38px;padding:8px 16px;border-radius:8px;text-decoration:none;font-size:13px;font-weight:550;border:1px solid transparent;line-height:1.5;transition:background .14s}.lark-primary{background:var(--lark-blue);color:#fff}.lark-primary:hover{background:#3559ca}.lark-secondary{color:#485b79;background:var(--lark-surface);border-color:#dce3ed}.lark-secondary:hover{background:var(--lark-soft)}.lark-quiet{background:transparent;color:var(--lark-muted);font-weight:450}.lark-quiet:hover{background:var(--lark-soft)}.lark-danger{color:#ad4949;border-color:#e5bcbc;background:var(--lark-surface)}.lark-text-button{display:inline-flex;align-items:center;gap:7px;background:transparent;border:0;padding:5px 0;color:#5875a5;font-size:12px}.lark-entry{display:flex;flex-direction:column;align-items:center;gap:10px}.lark-entry .lark-primary{min-width:200px}.lark-actions{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-top:23px}.lark-spread{justify-content:space-between}.lark-center{justify-content:center}.lark-footnote{color:var(--lark-muted);font-size:11px;text-align:center;margin:18px 0 0}.lark-left{text-align:left}.lark-bottom-note{text-align:center;color:var(--lark-muted);font-size:11px;margin:15px 0 0}.lark-steps{display:flex;list-style:none;gap:12px;margin:0 0 25px;padding:0;color:#8b98ad;font-size:11px}.lark-steps li{display:flex;gap:6px;align-items:center;white-space:nowrap}.lark-steps li+li:before{content:"";width:23px;height:1px;background:var(--lark-line);margin-right:5px}.lark-steps li span{width:18px;height:18px;display:grid;place-items:center;border-radius:50%;background:var(--lark-soft);font-size:10px}.lark-steps .active{color:var(--lark-blue);font-weight:600}.lark-steps .active span{background:#edf2ff}.lark-steps .done{color:#248470}.lark-steps .done span{background:#edf8f4}.lark-qr-layout{display:flex;align-items:center;gap:25px;margin:23px 0}.lark-qr{position:relative;width:166px;height:166px;padding:10px;background:white;border:1px solid var(--lark-line);border-radius:12px;flex-shrink:0}.lark-qr img{width:100%;height:100%;object-fit:contain}.lark-qr.expired img{opacity:.12}.lark-qr>span{position:absolute;inset:0;display:grid;place-items:center;color:#64758e;font-weight:600}.lark-qr-layout strong{font-weight:600;font-size:13px}.lark-qr-layout .lark-copy{margin:7px 0 10px}.lark-time{color:var(--lark-muted);font-size:11px;font-variant-numeric:tabular-nums}.lark-link-details{border-top:1px solid var(--lark-line);padding:13px 0 0;font-size:12px;color:var(--lark-muted)}.lark-link-details summary{margin-bottom:12px}.lark-raw-url{font-size:10px;line-height:1.65;overflow-wrap:anywhere;margin-top:10px;color:var(--lark-muted);user-select:all}.lark-notice{padding:13px 15px;background:#eef3fe;border-radius:9px;color:#526c95;font-size:12px;line-height:1.8}.lark-notice.warning{background:#fff7e9;color:#8b671e}.lark-state-icon{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:#edf8f4;color:#248470;font-size:20px;margin:2px 0 18px}.lark-state-icon.warning{background:#fff7e9;color:#9b7530}.lark-checks{padding:0;list-style:none;margin:20px 0;font-size:13px;color:var(--lark-muted)}.lark-checks li{padding:10px 0;border-bottom:1px solid var(--lark-line)}.lark-checks li:before{content:"\xB7";margin-right:12px}.lark-pairing{background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:11px;padding:17px 18px;display:flex;align-items:center;justify-content:space-between;gap:13px}.lark-pair-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:21px;letter-spacing:.02em;color:#3d5a91;margin:4px 0 2px;overflow-wrap:anywhere;user-select:all}.lark-pairing.expired .lark-pair-code{opacity:.5}.lark-caption{font-size:11px;color:var(--lark-muted)}.lark-copy-status{min-height:18px;font-size:11px;color:#248470;margin:5px 0}.lark-instructions{padding-left:20px;color:#536784;font-size:13px}.lark-instructions li{padding:3px 0}.lark-owner{padding:17px 18px;background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:10px;display:grid;gap:6px}.lark-owner strong{font-size:15px;font-weight:600;overflow-wrap:anywhere}.lark-connected-row{border-top:1px solid var(--lark-line);border-bottom:1px solid var(--lark-line);display:flex;justify-content:space-between;align-items:center;gap:16px;padding:16px 0;margin:26px 0 0}.lark-connected-row strong{font-size:12px;font-weight:600}.lark-connected-row p{font-size:11px;color:var(--lark-muted);margin:3px 0 0}.lark-advanced{font-size:12px;color:var(--lark-muted);padding-top:14px}.lark-advanced p{margin:9px 0 0;line-height:1.9}.lark-disconnect{border:1px solid #eacbcb;padding:14px;border-radius:10px;margin-top:16px;font-size:12px}.lark-disconnect p{margin:0}.lark-disconnect .lark-actions{margin-top:12px}.lark-profile-list{border:0;margin:20px 0 0;padding:0;display:grid;gap:10px;min-width:0}.lark-profile{display:flex;align-items:center;gap:12px;border:1px solid var(--lark-line);border-radius:10px;padding:15px;background:var(--lark-soft);cursor:pointer;overflow-wrap:anywhere}.lark-profile input{accent-color:var(--lark-blue)}.lark-error{display:flex;align-items:center;justify-content:space-between;gap:12px;background:#fff1f1;border:1px solid #f0d5d5;color:#9b4e4e;border-radius:9px;padding:11px 13px;font-size:12px;margin-bottom:21px}.lark-error .lark-text-button{flex-shrink:0;color:#9b4e4e}.lark-loading{padding:27px 0;text-align:center}.lark-loading .lark-spinner{margin:0 auto 17px;display:block}.lark-loading p{font-size:13px;color:var(--lark-muted);margin:0}.lark-loading-row{display:flex;align-items:center;gap:12px;color:var(--lark-muted);font-size:13px;margin:4px 0 15px}.lark-loading-row h3{color:var(--lark-ink);margin:0}.lark-spinner{display:inline-block;width:18px;height:18px;border:2px solid #dfe6f4;border-top-color:var(--lark-blue);border-radius:50%;animation:lark-spin .85s linear infinite;flex-shrink:0}.lark-pending{display:flex;align-items:center;gap:9px;margin-top:19px;font-size:11px;color:var(--lark-muted)}.lark-pending .lark-spinner{width:13px;height:13px}.lark-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.lark-update{border:1px solid #cfdcf8;background:#f4f7ff;border-radius:11px;padding:14px 16px;margin:0 0 22px}.lark-update-row{display:flex;align-items:center;justify-content:space-between;gap:14px}.lark-update strong{font-size:13px;font-weight:600}.lark-update p{font-size:12px;color:var(--lark-muted);margin:3px 0 0}.lark-update .lark-instructions{margin:12px 0 0}.lark-update .lark-actions{margin-top:12px}.lark-update .lark-copy-status{margin:6px 0 0}.lark-command{display:block;margin-top:6px;padding:8px 10px;border-radius:7px;background:var(--lark-surface);border:1px solid var(--lark-line);font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere;user-select:all}
|
|
696
|
+
.dsh-lark-settings *{box-sizing:border-box}.dsh-lark-settings button,.dsh-lark-settings input{font:inherit}.dsh-lark-settings button,.dsh-lark-settings summary{cursor:pointer}.dsh-lark-settings button:disabled{opacity:.5;cursor:not-allowed}.dsh-lark-settings button:focus-visible,.dsh-lark-settings a:focus-visible,.dsh-lark-settings input:focus-visible,.dsh-lark-settings summary:focus-visible{outline:3px solid #a1b8ff;outline-offset:3px}.dsh-lark-settings a{overflow-wrap:anywhere}.lark-heading{margin:0 0 22px}.lark-heading h2{font-size:23px;font-weight:650;margin:0 0 5px;letter-spacing:-.4px}.lark-heading p,.lark-card-header p{color:var(--lark-muted);font-size:13px;margin:0}.lark-card{border:1px solid var(--lark-line);border-radius:15px;background:var(--lark-surface);overflow:hidden;box-shadow:0 2px 5px #27385504}.lark-card-header{display:flex;align-items:center;gap:12px;padding:19px 23px;border-bottom:1px solid var(--lark-line)}.lark-plugin-icon{width:43px;height:43px;object-fit:contain;flex-shrink:0}.lark-card-header h2{font-size:15px;font-weight:650;line-height:1.6;margin:0}.lark-card-header p{font-size:12px}.lark-status{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--lark-muted);white-space:nowrap}.lark-status i{width:6px;height:6px;border-radius:50%;background:#9da9bc}.lark-status.online{color:#248470}.lark-status.online i{background:#248470}.lark-body{padding:28px;position:relative}.lark-body h3{font-size:20px;font-weight:650;letter-spacing:-.25px;margin:0 0 9px;line-height:1.5}.lark-copy{color:var(--lark-muted);font-size:13px;margin:0 0 20px}.lark-centered{text-align:center;line-height:1.9}.lark-body .lark-intro-title{text-align:center;font-size:21px}.lark-brands{display:flex;justify-content:center;align-items:center;gap:16px;margin:5px 0 25px}.lark-brand{width:53px;height:53px;display:grid;place-items:center;border-radius:14px;background:var(--lark-soft);border:1px solid var(--lark-line)}.lark-brand img{width:37px;height:37px;object-fit:contain}.lark-brand-line{width:78px;height:1px;background:#dbe3ee;position:relative}.lark-brand-line:after{content:"";position:absolute;left:calc(50% - 3px);top:-3px;width:7px;height:7px;border-radius:50%;background:#a9bbdc;border:2px solid var(--lark-surface)}.lark-brands-connected .lark-brand-line{background:#99d1c1}.lark-brands-connected .lark-brand-line:after{background:#248470}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:38px;padding:8px 16px;border-radius:8px;text-decoration:none;font-size:13px;font-weight:550;border:1px solid transparent;line-height:1.5;transition:background .14s}.lark-primary{background:var(--lark-blue);color:#fff}.lark-primary:hover{background:#3559ca}.lark-secondary{color:#485b79;background:var(--lark-surface);border-color:#dce3ed}.lark-secondary:hover{background:var(--lark-soft)}.lark-quiet{background:transparent;color:var(--lark-muted);font-weight:450}.lark-quiet:hover{background:var(--lark-soft)}.lark-danger{color:#ad4949;border-color:#e5bcbc;background:var(--lark-surface)}.lark-text-button{display:inline-flex;align-items:center;gap:7px;background:transparent;border:0;padding:5px 0;color:#5875a5;font-size:12px}.lark-entry{display:flex;flex-direction:column;align-items:center;gap:10px}.lark-entry .lark-primary{min-width:200px}.lark-actions{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-top:23px}.lark-spread{justify-content:space-between}.lark-center{justify-content:center}.lark-footnote{color:var(--lark-muted);font-size:11px;text-align:center;margin:18px 0 0}.lark-left{text-align:left}.lark-bottom-note{text-align:center;color:var(--lark-muted);font-size:11px;margin:15px 0 0}.lark-steps{display:flex;list-style:none;gap:12px;margin:0 0 25px;padding:0;color:#8b98ad;font-size:11px}.lark-steps li{display:flex;gap:6px;align-items:center;white-space:nowrap}.lark-steps li+li:before{content:"";width:23px;height:1px;background:var(--lark-line);margin-right:5px}.lark-steps li span{width:18px;height:18px;display:grid;place-items:center;border-radius:50%;background:var(--lark-soft);font-size:10px}.lark-steps .active{color:var(--lark-blue);font-weight:600}.lark-steps .active span{background:#edf2ff}.lark-steps .done{color:#248470}.lark-steps .done span{background:#edf8f4}.lark-qr-layout{display:flex;align-items:center;gap:25px;margin:23px 0}.lark-qr{position:relative;width:166px;height:166px;padding:10px;background:white;border:1px solid var(--lark-line);border-radius:12px;flex-shrink:0}.lark-qr img{width:100%;height:100%;object-fit:contain}.lark-qr.expired img{opacity:.12}.lark-qr>span{position:absolute;inset:0;display:grid;place-items:center;color:#64758e;font-weight:600}.lark-qr-layout strong{font-weight:600;font-size:13px}.lark-qr-layout .lark-copy{margin:7px 0 10px}.lark-time{color:var(--lark-muted);font-size:11px;font-variant-numeric:tabular-nums}.lark-link-details{border-top:1px solid var(--lark-line);padding:13px 0 0;font-size:12px;color:var(--lark-muted)}.lark-link-details summary{margin-bottom:12px}.lark-raw-url{font-size:10px;line-height:1.65;overflow-wrap:anywhere;margin-top:10px;color:var(--lark-muted);user-select:all}.lark-notice{padding:13px 15px;background:#eef3fe;border-radius:9px;color:#526c95;font-size:12px;line-height:1.8}.lark-notice.warning{background:#fff7e9;color:#8b671e}.lark-state-icon{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:#edf8f4;color:#248470;font-size:20px;margin:2px 0 18px}.lark-state-icon.warning{background:#fff7e9;color:#9b7530}.lark-checks{padding:0;list-style:none;margin:20px 0;font-size:13px;color:var(--lark-muted)}.lark-checks li{padding:10px 0;border-bottom:1px solid var(--lark-line)}.lark-checks li:before{content:"\xB7";margin-right:12px}.lark-pairing{background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:11px;padding:17px 18px;display:flex;align-items:center;justify-content:space-between;gap:13px}.lark-pair-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:21px;letter-spacing:.02em;color:#3d5a91;margin:4px 0 2px;overflow-wrap:anywhere;user-select:all}.lark-pairing.expired .lark-pair-code{opacity:.5}.lark-caption{font-size:11px;color:var(--lark-muted)}.lark-copy-status{min-height:18px;font-size:11px;color:#248470;margin:5px 0}.lark-instructions{padding-left:20px;color:#536784;font-size:13px}.lark-instructions li{padding:3px 0}.lark-owner{padding:17px 18px;background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:10px;display:grid;gap:6px}.lark-owner strong{font-size:15px;font-weight:600;overflow-wrap:anywhere}.lark-connected-row{border-top:1px solid var(--lark-line);border-bottom:1px solid var(--lark-line);display:flex;justify-content:space-between;align-items:center;gap:16px;padding:16px 0;margin:26px 0 0}.lark-connected-row strong{font-size:12px;font-weight:600}.lark-connected-row p{font-size:11px;color:var(--lark-muted);margin:3px 0 0}.lark-advanced{font-size:12px;color:var(--lark-muted);padding-top:14px}.lark-advanced p{margin:9px 0 0;line-height:1.9}.lark-disconnect{border:1px solid #eacbcb;padding:14px;border-radius:10px;margin-top:16px;font-size:12px}.lark-disconnect p{margin:0}.lark-disconnect .lark-actions{margin-top:12px}.lark-profile-list{border:0;margin:20px 0 0;padding:0;display:grid;gap:10px;min-width:0}.lark-profile{display:flex;align-items:center;gap:12px;border:1px solid var(--lark-line);border-radius:10px;padding:15px;background:var(--lark-soft);cursor:pointer;overflow-wrap:anywhere}.lark-profile input{accent-color:var(--lark-blue)}.lark-error{display:flex;align-items:center;justify-content:space-between;gap:12px;background:#fff1f1;border:1px solid #f0d5d5;color:#9b4e4e;border-radius:9px;padding:11px 13px;font-size:12px;margin-bottom:21px}.lark-error .lark-text-button{flex-shrink:0;color:#9b4e4e}.lark-loading{padding:27px 0;text-align:center}.lark-loading .lark-spinner{margin:0 auto 17px;display:block}.lark-loading p{font-size:13px;color:var(--lark-muted);margin:0}.lark-loading-row{display:flex;align-items:center;gap:12px;color:var(--lark-muted);font-size:13px;margin:4px 0 15px}.lark-loading-row h3{color:var(--lark-ink);margin:0}.lark-spinner{display:inline-block;width:18px;height:18px;border:2px solid #dfe6f4;border-top-color:var(--lark-blue);border-radius:50%;animation:lark-spin .85s linear infinite;flex-shrink:0}.lark-pending{display:flex;align-items:center;gap:9px;margin-top:19px;font-size:11px;color:var(--lark-muted)}.lark-pending .lark-spinner{width:13px;height:13px}.lark-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.lark-update{border:1px solid #cfdcf8;background:#f4f7ff;border-radius:11px;padding:14px 16px;margin:0 0 22px}.lark-connected-row+.lark-update{margin:16px 0 0}.lark-update-row{display:flex;align-items:center;justify-content:space-between;gap:14px}.lark-update strong{font-size:13px;font-weight:600}.lark-update p{font-size:12px;color:var(--lark-muted);margin:3px 0 0}.lark-update .lark-instructions{margin:12px 0 0}.lark-update .lark-actions{margin-top:12px}.lark-update .lark-copy-status{margin:6px 0 0}.lark-command{display:block;margin-top:6px;padding:8px 10px;border-radius:7px;background:var(--lark-surface);border:1px solid var(--lark-line);font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere;user-select:all}
|
|
675
697
|
@keyframes lark-spin{to{transform:rotate(360deg)}}
|
|
676
698
|
@media(prefers-color-scheme:dark){.dsh-lark-settings{--lark-ink:#e0e7f1;--lark-muted:#96a6bd;--lark-line:#364357;--lark-surface:#202a39;--lark-soft:#283448;--lark-blue:#5077ec}.lark-secondary{color:#c6d5ed;border-color:#46536b}.lark-pair-code{color:#b9ceff}.lark-instructions{color:#b0c2de}.lark-notice{background:#273d5a;color:#c2d3ec}.lark-notice.warning,.lark-state-icon.warning{background:#44391f;color:#e4c886}.lark-error{background:#492e32;border-color:#684047;color:#edbec2}.lark-error .lark-text-button{color:#edbec2}.lark-steps .active span{background:#293e6b}.lark-steps .done span,.lark-state-icon{background:#23483f}.lark-status.online,.lark-steps .done{color:#78c5b0}.lark-update{background:#24324a;border-color:#3a4d70}}
|
|
677
699
|
@media(max-width:520px){.lark-heading{margin-bottom:17px}.lark-card-header{padding:16px;gap:10px}.lark-card-header p{display:none}.lark-plugin-icon{width:37px;height:37px}.lark-body{padding:21px 18px}.lark-steps{gap:7px}.lark-steps li+li:before{width:12px;margin-right:2px}.lark-qr-layout{gap:17px;align-items:flex-start}.lark-qr{width:138px;height:138px}.lark-qr-layout strong{font-size:12px}.lark-qr-layout .lark-copy{font-size:12px}.lark-pairing{align-items:flex-start;flex-direction:column}.lark-pair-code{font-size:19px}.lark-body h3{font-size:19px}.lark-connected-row{gap:10px}.lark-actions{gap:8px}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{padding:8px 12px}.lark-copy{font-size:12px}.lark-update-row{flex-direction:column;align-items:flex-start}}
|
package/dist/config.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export interface BotConfig {
|
|
|
19
19
|
provider?: string;
|
|
20
20
|
model?: string;
|
|
21
21
|
users: UserPolicy[];
|
|
22
|
-
|
|
22
|
+
/** Optional cap on a restricted turn; unset, a turn runs until done or /stop. */
|
|
23
|
+
turnTimeoutMs?: number;
|
|
23
24
|
idleTimeoutMs: number;
|
|
24
25
|
maxSessions: number;
|
|
25
26
|
maxSessionsPerUser: number;
|
package/dist/config.js
CHANGED
|
@@ -73,8 +73,8 @@ export function parseConfig(value, baseDir = process.cwd()) {
|
|
|
73
73
|
provider: data.provider === undefined ? undefined : string(data.provider, 'provider'),
|
|
74
74
|
model: data.model === undefined ? undefined : string(data.model, 'model'),
|
|
75
75
|
users,
|
|
76
|
-
//
|
|
77
|
-
|
|
76
|
+
// An agent's work can take an hour. Only an administrator's explicit cap bounds restricted turns.
|
|
77
|
+
...(data.turnTimeoutMs === undefined ? {} : { turnTimeoutMs: integer(data.turnTimeoutMs, 0, 1000, 1_800_000, 'turnTimeoutMs') }),
|
|
78
78
|
idleTimeoutMs: integer(data.idleTimeoutMs, 1_800_000, 1000, 86_400_000, 'idleTimeoutMs'),
|
|
79
79
|
maxSessions: integer(data.maxSessions, 100, 1, 1000, 'maxSessions'),
|
|
80
80
|
maxSessionsPerUser: integer(data.maxSessionsPerUser, 10, 1, 100, 'maxSessionsPerUser'),
|
package/dist/live-reply.js
CHANGED
|
@@ -14,6 +14,9 @@ const TRUNCATED = '回复超过了飞书卡片的长度上限,后面的内容
|
|
|
14
14
|
const grey = (markdown) => `<font color='grey'>${markdown}</font>`;
|
|
15
15
|
const bytes = (value) => Buffer.byteLength(typeof value === 'string' ? value : JSON.stringify(value), 'utf8');
|
|
16
16
|
const code = (value) => '`' + value.replace(/`/g, 'ˋ') + '`';
|
|
17
|
+
// A harness turn may run for an hour; past a minute, seconds stop being readable.
|
|
18
|
+
const elapsed = (seconds) => seconds < 60 ? `${seconds} 秒`
|
|
19
|
+
: seconds < 3600 ? `${Math.floor(seconds / 60)} 分 ${seconds % 60} 秒` : `${Math.floor(seconds / 3600)} 小时 ${Math.floor(seconds % 3600 / 60)} 分`;
|
|
17
20
|
/** The App's step labels and a matching Feishu icon, by tool name. */
|
|
18
21
|
function activity(name) {
|
|
19
22
|
const is = (...names) => names.includes(name);
|
|
@@ -404,7 +407,7 @@ export class LiveReply {
|
|
|
404
407
|
}
|
|
405
408
|
panelTitle() {
|
|
406
409
|
const tools = this.tools ? ` · ${this.tools} 次工具调用` : '';
|
|
407
|
-
const seconds = (end) => Math.max(1, Math.round(((end ?? this.now()) - this.startedAt) / 1000));
|
|
410
|
+
const seconds = (end) => elapsed(Math.max(1, Math.round(((end ?? this.now()) - this.startedAt) / 1000)));
|
|
408
411
|
switch (this.phase) {
|
|
409
412
|
case 'thinking': {
|
|
410
413
|
const running = [...this.steps].reverse().find(step => step.kind === 'tool' && step.state === 'running');
|
|
@@ -413,10 +416,10 @@ export class LiveReply {
|
|
|
413
416
|
const detail = running.detail ? plainLabel(running.detail, 40) : '';
|
|
414
417
|
return `${activity(running.name).doing}${detail ? ` · ${detail}` : '…'}`;
|
|
415
418
|
}
|
|
416
|
-
case 'answering': return `已思考 ${seconds(this.answeredAt)}
|
|
417
|
-
case 'done': return `已完成,用时 ${seconds(this.finishedAt)}
|
|
418
|
-
case 'stopped': return `已停止,用时 ${seconds(this.finishedAt)}
|
|
419
|
-
case 'failed': return `处理失败,用时 ${seconds(this.finishedAt)}
|
|
419
|
+
case 'answering': return `已思考 ${seconds(this.answeredAt)}${tools}`;
|
|
420
|
+
case 'done': return `已完成,用时 ${seconds(this.finishedAt)}${tools}`;
|
|
421
|
+
case 'stopped': return `已停止,用时 ${seconds(this.finishedAt)}${tools}`;
|
|
422
|
+
case 'failed': return `处理失败,用时 ${seconds(this.finishedAt)}${tools}`;
|
|
420
423
|
}
|
|
421
424
|
}
|
|
422
425
|
/** The folded process panel. Readers open it when they want the thinking and tool steps. */
|
|
@@ -39,6 +39,8 @@ export declare class ConnectionController {
|
|
|
39
39
|
private unsafeStop;
|
|
40
40
|
private menuReady;
|
|
41
41
|
private documentsReady;
|
|
42
|
+
/** Menu entries the published app lacks; the owner adds them in the developer console. */
|
|
43
|
+
private menuMissing;
|
|
42
44
|
private documentSetup?;
|
|
43
45
|
private documentJob?;
|
|
44
46
|
private documentAborter?;
|
|
@@ -9,6 +9,7 @@ import { LockHeldError } from '../lock.js';
|
|
|
9
9
|
import { BOT_SCOPES, CARD_SCOPE, LarkPlatformError } from './lark-platform.js';
|
|
10
10
|
import { createChallenge, PairingError, waitForPairing } from './pairing.js';
|
|
11
11
|
import { defaultScopesReady } from '../rich-scopes.js';
|
|
12
|
+
import { missingBotMenuEntries } from '../bot-menu.js';
|
|
12
13
|
const restoreDelay = (attempt) => Math.min(10 * 60_000, 5_000 * 2 ** Math.min(attempt, 10));
|
|
13
14
|
const messages = {
|
|
14
15
|
idle: '把助手带到飞书,先用飞书确认创建。',
|
|
@@ -75,6 +76,8 @@ export class ConnectionController {
|
|
|
75
76
|
unsafeStop = false;
|
|
76
77
|
menuReady = false;
|
|
77
78
|
documentsReady = false;
|
|
79
|
+
/** Menu entries the published app lacks; the owner adds them in the developer console. */
|
|
80
|
+
menuMissing = [];
|
|
78
81
|
documentSetup;
|
|
79
82
|
documentJob;
|
|
80
83
|
documentAborter;
|
|
@@ -87,7 +90,8 @@ export class ConnectionController {
|
|
|
87
90
|
}
|
|
88
91
|
snapshot() {
|
|
89
92
|
return structuredClone({ ...this.state,
|
|
90
|
-
capabilities: { ...this.state.capabilities, documents: this.documentsReady }, ...(this.documentSetup ? { documentSetup: this.documentSetup } : {})
|
|
93
|
+
capabilities: { ...this.state.capabilities, documents: this.documentsReady }, ...(this.documentSetup ? { documentSetup: this.documentSetup } : {}),
|
|
94
|
+
...(this.record && this.menuMissing.length ? { menu: { missing: this.menuMissing, url: this.consoleUrl() } } : {}) });
|
|
91
95
|
}
|
|
92
96
|
phase(phase, message = messages[phase]) {
|
|
93
97
|
this.state = { phase, message, busy: ['creating', 'configuring', 'starting'].includes(phase),
|
|
@@ -168,6 +172,7 @@ export class ConnectionController {
|
|
|
168
172
|
if (!aborter.signal.aborted && !this.closed && this.record?.appId === record.appId) {
|
|
169
173
|
if (inspection.appId === record.appId && (!record.botOpenId || inspection.botOpenId === record.botOpenId)) {
|
|
170
174
|
this.documentsReady = inspection.scopeStatus === 'verified' && defaultScopesReady(inspection.grantedScopes);
|
|
175
|
+
this.menuMissing = missingBotMenuEntries(inspection.menuEventKeys ?? []);
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
}).catch(() => { })
|
|
@@ -353,6 +358,7 @@ export class ConnectionController {
|
|
|
353
358
|
let record = this.record;
|
|
354
359
|
let inspection = await this.deps.platform.inspect(record.profile, signal);
|
|
355
360
|
this.documentsReady = inspection.scopeStatus === 'verified' && defaultScopesReady(inspection.grantedScopes);
|
|
361
|
+
this.menuMissing = missingBotMenuEntries(inspection.menuEventKeys ?? []);
|
|
356
362
|
this.assertCurrent(signal);
|
|
357
363
|
if (inspection.appId !== record.appId || (record.botOpenId && inspection.botOpenId && record.botOpenId !== inspection.botOpenId)) {
|
|
358
364
|
throw new SetupError('机器人身份发生变化,请断开后重新绑定。', false);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import { ConnectionController } from './controller.js';
|
|
3
3
|
import type { ConnectionResult } from './protocol.js';
|
|
4
|
+
import { type Release } from './updates.js';
|
|
4
5
|
interface Peer {
|
|
5
6
|
principal?: {
|
|
6
7
|
kind?: string;
|
|
@@ -29,11 +30,11 @@ export declare function registerConnectionRpc(rpc: RpcHost, controller: Connecti
|
|
|
29
30
|
export declare function findLarkExecutable(explicit?: string): Promise<string>;
|
|
30
31
|
/**
|
|
31
32
|
* Attach the control plane without requiring any user-written team configuration.
|
|
32
|
-
* `
|
|
33
|
+
* `latestRelease` replaces the npm lookup behind the upgrade notice (tests use it to stay offline).
|
|
33
34
|
*/
|
|
34
35
|
export declare function applyOnboarding(ctx: Context, options?: {
|
|
35
36
|
dataDir?: string;
|
|
36
37
|
executable?: string;
|
|
37
|
-
|
|
38
|
+
latestRelease?: (signal: AbortSignal) => Promise<Release | undefined>;
|
|
38
39
|
}): Promise<void>;
|
|
39
40
|
export {};
|
package/dist/onboarding/host.js
CHANGED
|
@@ -203,7 +203,7 @@ async function waitReady(service, signal) {
|
|
|
203
203
|
}
|
|
204
204
|
/**
|
|
205
205
|
* Attach the control plane without requiring any user-written team configuration.
|
|
206
|
-
* `
|
|
206
|
+
* `latestRelease` replaces the npm lookup behind the upgrade notice (tests use it to stay offline).
|
|
207
207
|
*/
|
|
208
208
|
export async function applyOnboarding(ctx, options = {}) {
|
|
209
209
|
const connection = ctx.get('connection');
|
|
@@ -260,7 +260,7 @@ export async function applyOnboarding(ctx, options = {}) {
|
|
|
260
260
|
}
|
|
261
261
|
}, });
|
|
262
262
|
const installed = installedPackage();
|
|
263
|
-
const updates = new UpdateCheck(installed?.version, options.
|
|
263
|
+
const updates = new UpdateCheck(installed?.version, options.latestRelease
|
|
264
264
|
?? (signal => installed ? npmLatest(installed.name, signal) : Promise.resolve(undefined)));
|
|
265
265
|
const control = {
|
|
266
266
|
snapshot: () => withUpdate(controller.snapshot(), updates),
|
|
@@ -37,6 +37,8 @@ export interface Inspection {
|
|
|
37
37
|
pendingVersionId?: string;
|
|
38
38
|
publicationStatus: 'approved' | 'pending' | 'rejected' | 'draft' | 'unknown';
|
|
39
39
|
subscribedEvents?: string[];
|
|
40
|
+
/** Event keys of the shown bot menu in that version; empty when its menu is off. */
|
|
41
|
+
menuEventKeys?: string[];
|
|
40
42
|
/** Read-only CLI preflight evidence. Actual connection readiness is checked by the service. */
|
|
41
43
|
cardReady?: boolean;
|
|
42
44
|
menuReady?: boolean;
|
|
@@ -429,6 +429,11 @@ export class LarkPlatform {
|
|
|
429
429
|
if (Array.isArray(detail.events) && detail.events.every(v => typeof v === 'string' && /^[a-zA-Z0-9_.:-]{1,200}$/.test(v))) {
|
|
430
430
|
inspection.subscribedEvents = detail.events;
|
|
431
431
|
}
|
|
432
|
+
const bot = object(object(detail.ability)?.bot);
|
|
433
|
+
if (bot && Array.isArray(bot.bot_menus) && bot.bot_menus.length <= 64) {
|
|
434
|
+
inspection.menuEventKeys = bot.bot_menu_enable !== true ? [] : bot.bot_menus.map(node => object(node)?.event_key)
|
|
435
|
+
.filter((key) => typeof key === 'string' && /^[a-zA-Z0-9_.:-]{1,64}$/.test(key));
|
|
436
|
+
}
|
|
432
437
|
});
|
|
433
438
|
checkSignal(signal);
|
|
434
439
|
return inspection;
|
|
@@ -40,6 +40,15 @@ export interface ConnectionSnapshot {
|
|
|
40
40
|
expiresAt: number;
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
|
+
/** Bottom-menu entries the published app lacks, and where its owner adds them. */
|
|
44
|
+
menu?: {
|
|
45
|
+
missing: Array<{
|
|
46
|
+
label: string;
|
|
47
|
+
eventKey: string;
|
|
48
|
+
parent?: string;
|
|
49
|
+
}>;
|
|
50
|
+
url: string;
|
|
51
|
+
};
|
|
43
52
|
/** A newer published release of this plugin, with the installed version. */
|
|
44
53
|
update?: {
|
|
45
54
|
current: string;
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
/** A published release and when npm says it was published (epoch milliseconds). */
|
|
2
|
+
export interface Release {
|
|
3
|
+
version: string;
|
|
4
|
+
publishedAt: number;
|
|
5
|
+
}
|
|
1
6
|
/** True when `next` is a later release than `current`; a prerelease comes before its release. */
|
|
2
7
|
export declare function newerVersion(next: string, current: string): boolean;
|
|
3
|
-
/**
|
|
4
|
-
|
|
8
|
+
/**
|
|
9
|
+
* npm's `latest` release of a package and its publish time, when it names a stable release.
|
|
10
|
+
* Only the full package document carries publish times. Sends only the package name.
|
|
11
|
+
*/
|
|
12
|
+
export declare function npmLatest(name: string, signal: AbortSignal): Promise<Release | undefined>;
|
|
5
13
|
/**
|
|
6
14
|
* Whether a newer release of this plugin is available, for the settings page.
|
|
7
15
|
* A status read never waits for the registry: it returns what is known and
|
|
8
16
|
* starts a background check when one is due. Failures just show no notice.
|
|
17
|
+
* A release younger than a day is not offered yet, since installing it would
|
|
18
|
+
* still pick an older one; it shows up once it ages, without another check.
|
|
9
19
|
*/
|
|
10
20
|
export declare class UpdateCheck {
|
|
11
21
|
private readonly current;
|
|
@@ -14,7 +24,7 @@ export declare class UpdateCheck {
|
|
|
14
24
|
private latest?;
|
|
15
25
|
private nextCheck;
|
|
16
26
|
private running;
|
|
17
|
-
constructor(current: string | undefined, lookup: (signal: AbortSignal) => Promise<
|
|
27
|
+
constructor(current: string | undefined, lookup: (signal: AbortSignal) => Promise<Release | undefined>, now?: () => number);
|
|
18
28
|
status(): {
|
|
19
29
|
current: string;
|
|
20
30
|
latest: string;
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
const CHECK_EVERY_MS = 6 * 60 * 60_000;
|
|
3
3
|
// A failed check (offline, blocked registry) waits this long before the next try.
|
|
4
4
|
const RETRY_AFTER_MS = 30 * 60_000;
|
|
5
|
+
// pnpm 11, which the App and `dsh plugin` install with, skips releases younger than a day
|
|
6
|
+
// (`minimumReleaseAge`) and installs an older one instead, so a release is offered only after that.
|
|
7
|
+
const INSTALLABLE_AFTER_MS = 24 * 60 * 60_000;
|
|
5
8
|
const VERSION = /^(\d{1,9})\.(\d{1,9})\.(\d{1,9})(?:-([0-9A-Za-z.-]{1,40}))?$/;
|
|
6
9
|
/** True when `next` is a later release than `current`; a prerelease comes before its release. */
|
|
7
10
|
export function newerVersion(next, current) {
|
|
@@ -15,19 +18,29 @@ export function newerVersion(next, current) {
|
|
|
15
18
|
}
|
|
16
19
|
return !a[4] && Boolean(b[4]);
|
|
17
20
|
}
|
|
18
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* npm's `latest` release of a package and its publish time, when it names a stable release.
|
|
23
|
+
* Only the full package document carries publish times. Sends only the package name.
|
|
24
|
+
*/
|
|
19
25
|
export async function npmLatest(name, signal) {
|
|
20
|
-
const response = await fetch(`https://registry.npmjs.org
|
|
26
|
+
const response = await fetch(`https://registry.npmjs.org/${name.replace('/', '%2f')}`, { signal, headers: { accept: 'application/json' } });
|
|
21
27
|
if (!response.ok)
|
|
22
28
|
return;
|
|
23
|
-
const
|
|
24
|
-
const latest = tags?.latest;
|
|
25
|
-
|
|
29
|
+
const document = await response.json();
|
|
30
|
+
const latest = document?.['dist-tags']?.latest;
|
|
31
|
+
if (typeof latest !== 'string' || !VERSION.test(latest) || latest.includes('-'))
|
|
32
|
+
return;
|
|
33
|
+
const time = document?.time;
|
|
34
|
+
const stamp = time && typeof time === 'object' ? time[latest] : undefined;
|
|
35
|
+
const publishedAt = typeof stamp === 'string' ? Date.parse(stamp) : Number.NaN;
|
|
36
|
+
return Number.isFinite(publishedAt) ? { version: latest, publishedAt } : undefined;
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
28
39
|
* Whether a newer release of this plugin is available, for the settings page.
|
|
29
40
|
* A status read never waits for the registry: it returns what is known and
|
|
30
41
|
* starts a background check when one is due. Failures just show no notice.
|
|
42
|
+
* A release younger than a day is not offered yet, since installing it would
|
|
43
|
+
* still pick an older one; it shows up once it ages, without another check.
|
|
31
44
|
*/
|
|
32
45
|
export class UpdateCheck {
|
|
33
46
|
current;
|
|
@@ -46,15 +59,18 @@ export class UpdateCheck {
|
|
|
46
59
|
return;
|
|
47
60
|
if (!this.running && this.now() >= this.nextCheck)
|
|
48
61
|
void this.refresh();
|
|
49
|
-
|
|
62
|
+
const latest = this.latest;
|
|
63
|
+
if (!latest || this.now() - latest.publishedAt < INSTALLABLE_AFTER_MS)
|
|
64
|
+
return;
|
|
65
|
+
return newerVersion(latest.version, this.current) ? { current: this.current, latest: latest.version } : undefined;
|
|
50
66
|
}
|
|
51
67
|
async refresh() {
|
|
52
68
|
this.running = true;
|
|
53
69
|
this.nextCheck = this.now() + RETRY_AFTER_MS;
|
|
54
70
|
try {
|
|
55
71
|
const latest = await this.lookup(AbortSignal.timeout(10_000));
|
|
56
|
-
if (latest && VERSION.test(latest)) {
|
|
57
|
-
this.latest = latest;
|
|
72
|
+
if (latest && VERSION.test(latest.version) && Number.isFinite(latest.publishedAt)) {
|
|
73
|
+
this.latest = { version: latest.version, publishedAt: latest.publishedAt };
|
|
58
74
|
this.nextCheck = this.now() + CHECK_EVERY_MS;
|
|
59
75
|
}
|
|
60
76
|
}
|