dsh-lark-agent 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,7 @@ 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).
56
+ 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
57
 
58
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#升级).
59
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`.
@@ -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
- * `latestVersion` replaces the npm lookup behind the upgrade notice (tests use it to stay offline).
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
- latestVersion?: (signal: AbortSignal) => Promise<string | undefined>;
38
+ latestRelease?: (signal: AbortSignal) => Promise<Release | undefined>;
38
39
  }): Promise<void>;
39
40
  export {};
@@ -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
- * `latestVersion` replaces the npm lookup behind the upgrade notice (tests use it to stay offline).
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.latestVersion
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),
@@ -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
- /** 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>;
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<string | undefined>, now?: () => number);
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
- /** npm's `latest` tag for a package, when it names a stable release. Sends only the package name. */
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/-/package/${name.replace('/', '%2f')}/dist-tags`, { signal, headers: { accept: 'application/json' } });
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 tags = await response.json();
24
- const latest = tags?.latest;
25
- return typeof latest === 'string' && VERSION.test(latest) && !latest.includes('-') ? latest : undefined;
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
- return this.latest && newerVersion(this.latest, this.current) ? { current: this.current, latest: this.latest } : undefined;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-lark-agent",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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",