dsh-lark-agent 0.3.0 → 0.3.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
@@ -53,6 +53,8 @@ Then restart `dsh web`.
53
53
 
54
54
  ### Update
55
55
 
56
+ When a new version is out, the Feishu settings page shows a notice with these steps (in the desktop app it also opens the plugin list for you).
57
+
56
58
  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#升级).
57
59
  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`.
58
60
  3. Click **恢复连接** (resume) and check that the bot replies.
@@ -1,6 +1,11 @@
1
1
  import type { ConnectionApi } from './api.js';
2
+ /** `app` is the desktop App; `openPlugins` switches to its plugin list and reports whether it could. */
2
3
  export interface ConnectionCardProps {
3
4
  api: ConnectionApi;
5
+ host?: {
6
+ app: boolean;
7
+ openPlugins?: () => boolean;
8
+ };
4
9
  }
5
10
  /** The host is authoritative for every transition, including readiness and identity. */
6
- export declare function ConnectionCard({ api }: ConnectionCardProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function ConnectionCard({ api, host }: ConnectionCardProps): import("react/jsx-runtime").JSX.Element;
@@ -20,6 +20,8 @@ export interface ClientContext {
20
20
  inject: () => ConnectionCardProps;
21
21
  }, component: ComponentType<ConnectionCardProps>): () => void;
22
22
  };
23
+ /** Optional services by name, such as the App's plugin page navigation. */
24
+ get?(name: string): unknown;
23
25
  }
24
26
  export declare const inject: string[];
25
27
  /** Add a dedicated settings tab; both host versions own this public slot. */
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.update !== void 0) {
115
+ const update = object(data.update);
116
+ const version = (value2) => {
117
+ const raw = text(value2, 64);
118
+ if (!/^\d{1,9}\.\d{1,9}\.\d{1,9}(?:-[0-9A-Za-z.-]{1,40})?$/.test(raw)) throw new Error("invalid version");
119
+ return raw;
120
+ };
121
+ snapshot.update = { current: version(update.current), latest: version(update.latest) };
122
+ }
114
123
  return snapshot;
115
124
  }
116
125
  function parseProfiles(value) {
@@ -275,6 +284,8 @@ var deepseek_harness_icon_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEU
275
284
 
276
285
  // src/client/ConnectionCard.tsx
277
286
  var import_jsx_runtime = require("react/jsx-runtime");
287
+ var UPGRADE_COMMAND = "dsh plugin --profile web update dsh-lark-agent@latest";
288
+ var UPGRADE_GUIDE = "https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/DEVELOPMENT.md#\u5347\u7EA7";
278
289
  var phaseLabels = {
279
290
  idle: "\u672A\u8FDE\u63A5",
280
291
  creating: "\u6B63\u5728\u521B\u5EFA",
@@ -319,7 +330,54 @@ function Steps({ active }) {
319
330
  label
320
331
  ] }, label)) });
321
332
  }
322
- function ConnectionCard({ api }) {
333
+ function UpdateNotice({ update, host, phase }) {
334
+ const [open, setOpen] = (0, import_react2.useState)(false);
335
+ const [status, setStatus] = (0, import_react2.useState)("");
336
+ const app = host?.app ?? false;
337
+ const settled = phase === "connected" || phase === "paused";
338
+ const copy = async () => {
339
+ try {
340
+ await navigator.clipboard.writeText(UPGRADE_COMMAND);
341
+ setStatus("\u5DF2\u590D\u5236\u5347\u7EA7\u547D\u4EE4");
342
+ } catch {
343
+ setStatus("\u8BF7\u9009\u4E2D\u4E0A\u65B9\u547D\u4EE4\u624B\u52A8\u590D\u5236");
344
+ }
345
+ };
346
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-update", children: [
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-update-row", children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
350
+ "\u98DE\u4E66\u63D2\u4EF6\u6709\u65B0\u7248\u672C ",
351
+ update.latest
352
+ ] }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
354
+ "\u5F53\u524D ",
355
+ update.current,
356
+ "\uFF0C\u5347\u7EA7\u540E\u4FDD\u7559\u73B0\u6709\u8FDE\u63A5\u548C\u5BF9\u8BDD\u3002"
357
+ ] })
358
+ ] }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", "aria-expanded": open, onClick: () => setOpen(!open), children: open ? "\u6536\u8D77" : "\u5347\u7EA7" })
360
+ ] }),
361
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ol", { className: "lark-instructions", children: [
363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: phase === "paused" ? "\u8FDE\u63A5\u5DF2\u6682\u505C\uFF0C\u53EF\u4EE5\u76F4\u63A5\u5347\u7EA7\u3002" : settled ? "\u5148\u70B9\u4E0B\u65B9\u300C\u6682\u505C\u8FDE\u63A5\u300D\u3002" : "\u5148\u5B8C\u6210\u6216\u53D6\u6D88\u5F53\u524D\u7684\u8FDE\u63A5\u6B65\u9AA4\u3002" }),
364
+ app ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "\u5728\u300C\u63D2\u4EF6\u5217\u8868\u300D\u5378\u8F7D dsh-lark-agent\uFF0C\u518D\u70B9\u300C\u6DFB\u52A0\u63D2\u4EF6\u300D\u8F93\u5165 dsh-lark-agent \u5B89\u88C5\u65B0\u7248\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
365
+ "\u5728\u7EC8\u7AEF\u8FD0\u884C\u4E0B\u9762\u7684\u547D\u4EE4\uFF0C\u7136\u540E\u91CD\u542F dsh web\uFF1A",
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "lark-command", children: UPGRADE_COMMAND })
367
+ ] }),
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: settled ? "\u56DE\u5230\u8FD9\u91CC\u70B9\u300C\u6062\u590D\u8FDE\u63A5\u300D\uFF0C\u5728\u98DE\u4E66\u53D1\u4E00\u53E5\u8BDD\u786E\u8BA4\u80FD\u56DE\u590D\u3002" : "\u5347\u7EA7\u540E\u56DE\u5230\u8FD9\u91CC\u7EE7\u7EED\u3002" })
369
+ ] }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-actions", children: [
371
+ app ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", onClick: () => {
372
+ if (!host?.openPlugins?.()) setStatus("\u8BF7\u5207\u5230\u4E0A\u65B9\u7684\u300C\u63D2\u4EF6\u5217\u8868\u300D\u7EE7\u7EED\u3002");
373
+ }, children: "\u6253\u5F00\u63D2\u4EF6\u5217\u8868" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", onClick: () => void copy(), children: "\u590D\u5236\u5347\u7EA7\u547D\u4EE4" }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(External, { className: "lark-text-button", url: UPGRADE_GUIDE, children: "\u5347\u7EA7\u524D\u7684\u5907\u4EFD\u8BF4\u660E" })
375
+ ] }),
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lark-copy-status", role: "status", children: status })
377
+ ] })
378
+ ] });
379
+ }
380
+ function ConnectionCard({ api, host }) {
323
381
  const { snapshot, error, pending, profiles, refresh, mutate, loadProfiles } = useConnection(api);
324
382
  const [chooseExisting, setChooseExisting] = (0, import_react2.useState)(false);
325
383
  const [selected, setSelected] = (0, import_react2.useState)("");
@@ -506,7 +564,7 @@ function ConnectionCard({ api }) {
506
564
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lark-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-quiet", disabled, onClick: cancel, children: "\u53D6\u6D88\u8FDE\u63A5" }) })
507
565
  ] });
508
566
  if (phase === "connected" || phase === "paused") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
509
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Steps, { active: 2 }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Steps, { active: 3 }),
510
568
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BrandLink, { connected: phase === "connected" }),
511
569
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "lark-intro-title", children: phase === "paused" ? "\u5DF2\u6682\u505C\u98DE\u4E66\u8FDE\u63A5" : "\u53EF\u4EE5\u5728\u98DE\u4E66\u627E\u6211\u4E86" }),
512
570
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lark-copy lark-centered", children: phase === "paused" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -598,6 +656,7 @@ function ConnectionCard({ api }) {
598
656
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error }),
599
657
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-text-button", disabled, onClick: refresh, children: "\u91CD\u8BD5" })
600
658
  ] }),
659
+ snapshot?.update && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdateNotice, { update: snapshot.update, host, phase }),
601
660
  body(),
602
661
  pending && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-pending", role: "status", children: [
603
662
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "lark-spinner", "aria-hidden": "true" }),
@@ -612,10 +671,10 @@ function ConnectionCard({ api }) {
612
671
  // src/client/styles.ts
613
672
  var css = `
614
673
  .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}
615
- .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}
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}
616
675
  @keyframes lark-spin{to{transform:rotate(360deg)}}
617
- @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}}
618
- @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}}
676
+ @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
+ @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}}
619
678
  @media(max-width:360px){.lark-qr-layout{flex-direction:column;align-items:center;text-align:center}.lark-steps{font-size:10px}.lark-steps li+li:before{width:8px}.lark-status{font-size:10px}}
620
679
  @media(prefers-reduced-motion:reduce){.dsh-lark-settings *{animation:none!important;transition:none!important}}
621
680
  `;
@@ -630,14 +689,25 @@ function installStyles() {
630
689
  // src/client/index.tsx
631
690
  var inject = ["slots", "connection"];
632
691
  function apply(ctx) {
633
- const api = new ConnectionApi(ctx.connection.rpc, ctx.connection.generation !== void 0);
692
+ const app = ctx.connection.generation !== void 0;
693
+ const api = new ConnectionApi(ctx.connection.rpc, app);
694
+ const openPlugins = () => {
695
+ try {
696
+ const navigation = ctx.get?.("pluginNavigation");
697
+ if (typeof navigation?.openList !== "function") return false;
698
+ navigation.openList();
699
+ return true;
700
+ } catch {
701
+ return false;
702
+ }
703
+ };
634
704
  ctx.effect(installStyles, "dsh-larkbot: connection styles");
635
705
  ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
636
706
  name: "settings.plugins.tab",
637
707
  id: "dsh-larkbot",
638
708
  order: 30,
639
709
  label: () => "\u98DE\u4E66",
640
- inject: () => ({ api })
710
+ inject: () => ({ api, host: { app, openPlugins } })
641
711
  }, ConnectionCard));
642
712
  }
643
713
  return module.exports; } });
@@ -27,9 +27,13 @@ export declare function lockedConnectionControl(): ConnectionControl;
27
27
  export declare function registerConnectionRpc(rpc: RpcHost, controller: ConnectionControl): () => Promise<void>;
28
28
  export declare function registerConnectionRpc(rpc: RpcHost, controller: ConnectionControl, ownerRoutes?: OwnerFetchRoutes): (() => Promise<void>) | Promise<() => Promise<void>>;
29
29
  export declare function findLarkExecutable(explicit?: string): Promise<string>;
30
- /** Attach the control plane without requiring any user-written team configuration. */
30
+ /**
31
+ * Attach the control plane without requiring any user-written team configuration.
32
+ * `latestVersion` replaces the npm lookup behind the upgrade notice (tests use it to stay offline).
33
+ */
31
34
  export declare function applyOnboarding(ctx: Context, options?: {
32
35
  dataDir?: string;
33
36
  executable?: string;
37
+ latestVersion?: (signal: AbortSignal) => Promise<string | undefined>;
34
38
  }): Promise<void>;
35
39
  export {};
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { access } from 'node:fs/promises';
3
- import { constants } from 'node:fs';
3
+ import { constants, readFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
5
  import { delimiter, join, resolve } from 'node:path';
6
6
  import { createBotService, BotServiceStartError } from '../bot-service.js';
@@ -9,6 +9,7 @@ import { acquireLock, LockHeldError } from '../lock.js';
9
9
  import { ConnectionController, SetupError } from './controller.js';
10
10
  import { ConnectionStore } from './store.js';
11
11
  import { LarkPlatform } from './lark-platform.js';
12
+ import { npmLatest, UpdateCheck } from './updates.js';
12
13
  const endpoints = new Set(['status', 'profiles', 'create', 'use-profile', 'recheck', 'cancel',
13
14
  'renew-pairing', 'confirm-owner', 'pause', 'resume', 'disconnect', 'enable-documents', 'cancel-documents']);
14
15
  const failure = (message) => ({ ok: false, error: { code: 'internal', message, details: {} } });
@@ -151,6 +152,21 @@ export async function findLarkExecutable(explicit) {
151
152
  }
152
153
  return 'lark-cli';
153
154
  }
155
+ /** This plugin's own package name and version, from the package it was installed from. */
156
+ function installedPackage() {
157
+ try {
158
+ const value = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
159
+ return typeof value.name === 'string' && typeof value.version === 'string' ? { name: value.name, version: value.version } : undefined;
160
+ }
161
+ catch {
162
+ return;
163
+ }
164
+ }
165
+ /** Every snapshot the page receives carries the upgrade notice once a newer release is known. */
166
+ function withUpdate(snapshot, updates) {
167
+ const update = updates.status();
168
+ return update ? { ...snapshot, update } : snapshot;
169
+ }
154
170
  function requireRuntime(ctx) {
155
171
  if (!checkRuntimeCompatibility(ctx).supported)
156
172
  throw new SetupError('当前 dsh 缺少所需的会话能力,请检查插件和持久化服务。', false);
@@ -185,7 +201,10 @@ async function waitReady(service, signal) {
185
201
  abort();
186
202
  });
187
203
  }
188
- /** Attach the control plane without requiring any user-written team configuration. */
204
+ /**
205
+ * Attach the control plane without requiring any user-written team configuration.
206
+ * `latestVersion` replaces the npm lookup behind the upgrade notice (tests use it to stay offline).
207
+ */
189
208
  export async function applyOnboarding(ctx, options = {}) {
190
209
  const connection = ctx.get('connection');
191
210
  if (!connection?.rpc)
@@ -240,9 +259,17 @@ export async function applyOnboarding(ctx, options = {}) {
240
259
  throw error;
241
260
  }
242
261
  }, });
262
+ const installed = installedPackage();
263
+ const updates = new UpdateCheck(installed?.version, options.latestVersion
264
+ ?? (signal => installed ? npmLatest(installed.name, signal) : Promise.resolve(undefined)));
265
+ const control = {
266
+ snapshot: () => withUpdate(controller.snapshot(), updates),
267
+ profiles: signal => controller.profiles(signal),
268
+ command: async (endpoint, payload) => withUpdate(await controller.command(endpoint, payload), updates),
269
+ };
243
270
  let remove;
244
271
  try {
245
- remove = await registerConnectionRpc(connection.rpc, controller, connection.fetch);
272
+ remove = await registerConnectionRpc(connection.rpc, control, connection.fetch);
246
273
  ctx.effect(() => {
247
274
  return async () => {
248
275
  await remove();
@@ -40,6 +40,11 @@ export interface ConnectionSnapshot {
40
40
  expiresAt: number;
41
41
  };
42
42
  };
43
+ /** A newer published release of this plugin, with the installed version. */
44
+ update?: {
45
+ current: string;
46
+ latest: string;
47
+ };
43
48
  }
44
49
  export interface ConnectionProfile {
45
50
  name: string;
@@ -0,0 +1,23 @@
1
+ /** True when `next` is a later release than `current`; a prerelease comes before its release. */
2
+ export declare function newerVersion(next: string, current: string): boolean;
3
+ /** npm's `latest` tag for a package, when it names a stable release. Sends only the package name. */
4
+ export declare function npmLatest(name: string, signal: AbortSignal): Promise<string | undefined>;
5
+ /**
6
+ * Whether a newer release of this plugin is available, for the settings page.
7
+ * A status read never waits for the registry: it returns what is known and
8
+ * starts a background check when one is due. Failures just show no notice.
9
+ */
10
+ export declare class UpdateCheck {
11
+ private readonly current;
12
+ private readonly lookup;
13
+ private readonly now;
14
+ private latest?;
15
+ private nextCheck;
16
+ private running;
17
+ constructor(current: string | undefined, lookup: (signal: AbortSignal) => Promise<string | undefined>, now?: () => number);
18
+ status(): {
19
+ current: string;
20
+ latest: string;
21
+ } | undefined;
22
+ private refresh;
23
+ }
@@ -0,0 +1,67 @@
1
+ // Read only while the settings page polls, and at most this often once a check succeeds.
2
+ const CHECK_EVERY_MS = 6 * 60 * 60_000;
3
+ // A failed check (offline, blocked registry) waits this long before the next try.
4
+ const RETRY_AFTER_MS = 30 * 60_000;
5
+ const VERSION = /^(\d{1,9})\.(\d{1,9})\.(\d{1,9})(?:-([0-9A-Za-z.-]{1,40}))?$/;
6
+ /** True when `next` is a later release than `current`; a prerelease comes before its release. */
7
+ export function newerVersion(next, current) {
8
+ const a = VERSION.exec(next), b = VERSION.exec(current);
9
+ if (!a || !b)
10
+ return false;
11
+ for (let index = 1; index <= 3; index++) {
12
+ const left = Number(a[index]), right = Number(b[index]);
13
+ if (left !== right)
14
+ return left > right;
15
+ }
16
+ return !a[4] && Boolean(b[4]);
17
+ }
18
+ /** npm's `latest` tag for a package, when it names a stable release. Sends only the package name. */
19
+ export async function npmLatest(name, signal) {
20
+ const response = await fetch(`https://registry.npmjs.org/-/package/${name.replace('/', '%2f')}/dist-tags`, { signal, headers: { accept: 'application/json' } });
21
+ if (!response.ok)
22
+ return;
23
+ const tags = await response.json();
24
+ const latest = tags?.latest;
25
+ return typeof latest === 'string' && VERSION.test(latest) && !latest.includes('-') ? latest : undefined;
26
+ }
27
+ /**
28
+ * Whether a newer release of this plugin is available, for the settings page.
29
+ * A status read never waits for the registry: it returns what is known and
30
+ * starts a background check when one is due. Failures just show no notice.
31
+ */
32
+ export class UpdateCheck {
33
+ current;
34
+ lookup;
35
+ now;
36
+ latest;
37
+ nextCheck = 0;
38
+ running = false;
39
+ constructor(current, lookup, now = Date.now) {
40
+ this.current = current;
41
+ this.lookup = lookup;
42
+ this.now = now;
43
+ }
44
+ status() {
45
+ if (!this.current || !VERSION.test(this.current))
46
+ return;
47
+ if (!this.running && this.now() >= this.nextCheck)
48
+ void this.refresh();
49
+ return this.latest && newerVersion(this.latest, this.current) ? { current: this.current, latest: this.latest } : undefined;
50
+ }
51
+ async refresh() {
52
+ this.running = true;
53
+ this.nextCheck = this.now() + RETRY_AFTER_MS;
54
+ try {
55
+ const latest = await this.lookup(AbortSignal.timeout(10_000));
56
+ if (latest && VERSION.test(latest)) {
57
+ this.latest = latest;
58
+ this.nextCheck = this.now() + CHECK_EVERY_MS;
59
+ }
60
+ }
61
+ catch { /* Offline or blocked: no notice, and a later poll tries again. */ }
62
+ finally {
63
+ this.running = false;
64
+ }
65
+ }
66
+ }
67
+ //# sourceMappingURL=updates.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-lark-agent",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Reach your DeepSeek Harness assistant from Feishu / Lark: a dsh plugin built on the official Lark CLI",
5
5
  "keywords": [
6
6
  "deepseek",