pi-lemonade-link 1.0.1 → 1.0.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 +6 -2
- package/index.ts +45 -14
- package/lemonade.example.json +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -411,7 +411,7 @@ The full shape (values shown are the built-in defaults):
|
|
|
411
411
|
| `barPollMs` | Server-poll cadence for the status bar's busy/queue + CPU/GPU/NPU/VRAM segments. `0` = local metrics only (instance, tok/s, cache-hit %). |
|
|
412
412
|
| `specCheck` / `specCheckCooldownMs` / `specCheckTimeoutMs` | Docs-freshness check: the `docs/` snapshot is compared against upstream `lemonade-sdk/lemonade` by git blob SHA — **TUI-only status, no startup warning** (extension ↔ server agreement is what matters; see *Keeping the specs honest*); the verdict is durable for `specCheckCooldownMs` (default 24 h, also the upstream-release probe's throttle). |
|
|
413
413
|
| `serverVersionCheck` | **Tier 1, startup warning**: `/v1/health` self-reports the server version; any instance reporting a version other than the build this extension was verified against (`v11.7.0` — a constant in the extension code, not a setting) warns **with direction**: server newer → check for an extension update; server older → update the server. |
|
|
414
|
-
| `upstreamVersionCheck` | **Tier 2, info notice**: servers older than the latest published lemonade release (probed via GitHub, throttled) get a one-line "a server update is available" notice. Default on; separately toggleable. |
|
|
414
|
+
| `upstreamVersionCheck` | **Tier 2, info notice**: servers older than the latest published lemonade release (probed via GitHub, throttled) get a one-line "a server update is available" notice — **deduplicated to once per (instance, release)**, so it never re-fires on every session for the same release; the TUI always shows current state. Default on; separately toggleable. |
|
|
415
415
|
|
|
416
416
|
## Offline & unreachable networks
|
|
417
417
|
|
|
@@ -561,7 +561,11 @@ Drift is watched **in tiers, ordered by what actually matters at runtime**:
|
|
|
561
561
|
server older than it gets a single info line — *"instance 'x' runs
|
|
562
562
|
lemonade v11.7.0; the latest published release is v11.9.0 — a server
|
|
563
563
|
update is available."* Much lower tier: an older server isn't broken,
|
|
564
|
-
it's just behind.
|
|
564
|
+
it's just behind. The notice is **deduplicated to once per (instance,
|
|
565
|
+
release)** — it fires when a new upstream release first appears, never
|
|
566
|
+
re-fires on every session for the same fact; `/lemonade-setup` →
|
|
567
|
+
Spec & version check always shows the current state.
|
|
568
|
+
`upstreamVersionCheck: false` disables.
|
|
565
569
|
3. **Docs drift — TUI-only status, deliberately no startup warning.** The
|
|
566
570
|
`docs/` snapshot is the reference for the version the extension targets;
|
|
567
571
|
as long as extension and server agree on version, whether upstream HEAD
|
package/index.ts
CHANGED
|
@@ -105,7 +105,8 @@ interface LemonadeConfig {
|
|
|
105
105
|
serverVersionCheck: boolean;
|
|
106
106
|
// Low-tier notice: compare each server's live version against the latest
|
|
107
107
|
// published lemonade release (GitHub releases); server older → one-line
|
|
108
|
-
// info notice
|
|
108
|
+
// info notice, deduplicated to once per (instance, new release) — the
|
|
109
|
+
// TUI always shows the live truth. Separately toggleable; default on.
|
|
109
110
|
upstreamVersionCheck: boolean;
|
|
110
111
|
discoveryTimeoutMs: number;
|
|
111
112
|
beaconTimeoutMs: number;
|
|
@@ -718,6 +719,11 @@ const GH_LATEST_RELEASE = "https://api.github.com/repos/lemonade-sdk/lemonade/re
|
|
|
718
719
|
interface UpstreamVersionSnapshot {
|
|
719
720
|
checkedAt: number;
|
|
720
721
|
latest: string;
|
|
722
|
+
/** Tier-2 notice dedup: instance → the latest release version at the
|
|
723
|
+
* time that instance was last noticed. "A server update is available"
|
|
724
|
+
* fires once per (instance, release) — never a per-session nag about
|
|
725
|
+
* the same fact; the TUI always shows the live truth. */
|
|
726
|
+
notified?: Record<string, string>;
|
|
721
727
|
}
|
|
722
728
|
|
|
723
729
|
function upstreamSnapshotPath(): string {
|
|
@@ -727,7 +733,11 @@ function upstreamSnapshotPath(): string {
|
|
|
727
733
|
function loadUpstreamSnapshot(): UpstreamVersionSnapshot | undefined {
|
|
728
734
|
try {
|
|
729
735
|
const raw = JSON.parse(fs.readFileSync(upstreamSnapshotPath(), "utf8")) as UpstreamVersionSnapshot;
|
|
730
|
-
|
|
736
|
+
const notifiedOk =
|
|
737
|
+
raw.notified === undefined ||
|
|
738
|
+
(typeof raw.notified === "object" && raw.notified !== null &&
|
|
739
|
+
Object.values(raw.notified).every((v) => typeof v === "string"));
|
|
740
|
+
return typeof raw.checkedAt === "number" && typeof raw.latest === "string" && notifiedOk ? raw : undefined;
|
|
731
741
|
} catch {
|
|
732
742
|
return undefined;
|
|
733
743
|
}
|
|
@@ -755,7 +765,7 @@ async function latestUpstreamVersion(config: LemonadeConfig): Promise<string | u
|
|
|
755
765
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
756
766
|
const tag = ((await res.json()) as { tag_name?: string }).tag_name;
|
|
757
767
|
if (typeof tag !== "string" || !tag) throw new Error("release carried no tag_name");
|
|
758
|
-
saveUpstreamSnapshot({ checkedAt: Date.now(), latest: tag.replace(/^v/, "") });
|
|
768
|
+
saveUpstreamSnapshot({ checkedAt: Date.now(), latest: tag.replace(/^v/, ""), notified: fresh?.notified });
|
|
759
769
|
} catch (error) {
|
|
760
770
|
console.error(
|
|
761
771
|
`[pi-lemonade-link] upstream-version probe skipped: ${error instanceof Error ? error.message : error}`
|
|
@@ -764,7 +774,7 @@ async function latestUpstreamVersion(config: LemonadeConfig): Promise<string | u
|
|
|
764
774
|
// pass per cooldown") — otherwise offline days re-probe at every
|
|
765
775
|
// session start. Keep the last known latest; a failed probe proves
|
|
766
776
|
// nothing new (unknown ≠ outdated).
|
|
767
|
-
saveUpstreamSnapshot({ checkedAt: Date.now(), latest: fresh?.latest ?? "" });
|
|
777
|
+
saveUpstreamSnapshot({ checkedAt: Date.now(), latest: fresh?.latest ?? "", notified: fresh?.notified });
|
|
768
778
|
}
|
|
769
779
|
}
|
|
770
780
|
return loadUpstreamSnapshot()?.latest;
|
|
@@ -842,6 +852,16 @@ function versionCompare(a: string, b: string): number | null {
|
|
|
842
852
|
return 0;
|
|
843
853
|
}
|
|
844
854
|
|
|
855
|
+
/** Whether the tier-2 "server update is available" notice is due for one
|
|
856
|
+
* instance: the server is behind the latest published release AND this
|
|
857
|
+
* exact release hasn't already been noticed for that instance — one
|
|
858
|
+
* notice per (instance, release), never a per-session nag about the
|
|
859
|
+
* same fact. Pure. */
|
|
860
|
+
function upstreamNoticeDue(serverVersion: string, latest: string, lastNotifiedLatest: string | undefined): boolean {
|
|
861
|
+
const cmp = versionCompare(serverVersion, latest);
|
|
862
|
+
return cmp !== null && cmp < 0 && lastNotifiedLatest !== latest;
|
|
863
|
+
}
|
|
864
|
+
|
|
845
865
|
/** Server-version drift collected during provider registration/refresh
|
|
846
866
|
* (registerInstanceProvider records; registerLemonadeProvider resets).
|
|
847
867
|
* direction: "ahead" = server newer than the extension was verified for
|
|
@@ -1012,15 +1032,24 @@ function registerSpecDriftWarnings(pi: ExtensionAPI, configRef: { current: Lemon
|
|
|
1012
1032
|
void latestUpstreamVersion(config)
|
|
1013
1033
|
.then((latest) => {
|
|
1014
1034
|
if (!latest) return;
|
|
1035
|
+
// Dedup: fire the notice once per (instance, release). The TUI
|
|
1036
|
+
// always shows the live truth; the session-start line never
|
|
1037
|
+
// repeats for the same release.
|
|
1038
|
+
const snap = loadUpstreamSnapshot();
|
|
1039
|
+
if (!snap) return;
|
|
1040
|
+
const notified: Record<string, string> = { ...(snap.notified ?? {}) };
|
|
1041
|
+
let dirty = false;
|
|
1015
1042
|
for (const [instance, serverVersion] of serverVersionsSeen) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
`
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1043
|
+
if (!upstreamNoticeDue(serverVersion, latest, notified[instance])) continue;
|
|
1044
|
+
ctx.ui.notify(
|
|
1045
|
+
`pi-lemonade-link: instance "${instance}" runs lemonade v${serverVersion}; the latest published release is v${latest} — a server update is available. ` +
|
|
1046
|
+
`(noted once per release; /lemonade-setup → Spec & version check always shows current status)`,
|
|
1047
|
+
"info"
|
|
1048
|
+
);
|
|
1049
|
+
notified[instance] = latest;
|
|
1050
|
+
dirty = true;
|
|
1023
1051
|
}
|
|
1052
|
+
if (dirty) saveUpstreamSnapshot({ ...snap, notified });
|
|
1024
1053
|
})
|
|
1025
1054
|
.catch((error) =>
|
|
1026
1055
|
console.error(`[pi-lemonade-link] upstream notice skipped: ${error instanceof Error ? error.message : error}`)
|
|
@@ -3002,7 +3031,7 @@ function registerSetupCommand(pi: ExtensionAPI, configRef: { current: LemonadeCo
|
|
|
3002
3031
|
const c = upstreamSnap.latest ? versionCompare(v, upstreamSnap.latest) : null;
|
|
3003
3032
|
return c !== null && c < 0;
|
|
3004
3033
|
})
|
|
3005
|
-
? "update available — at least one server is behind the latest published release"
|
|
3034
|
+
? "update available — at least one server is behind the latest published release (notice fires once per release)"
|
|
3006
3035
|
: `all servers current with the latest published release (v${upstreamSnap.latest})`
|
|
3007
3036
|
: "not probed yet";
|
|
3008
3037
|
const sub = await menu(ctx, "Spec & Version Check", [
|
|
@@ -3011,7 +3040,7 @@ function registerSetupCommand(pi: ExtensionAPI, configRef: { current: LemonadeCo
|
|
|
3011
3040
|
{ value: "resync", label: "Re-sync docs/ from upstream", description: "Download the current docs/api markdown into docs/ — ground truth only, the extension code is NOT updated" },
|
|
3012
3041
|
{ value: "toggledocs", label: `Toggle docs check: ${config.specCheck ? "on → off" : "off → on"}`, description: `specCheck (currently ${config.specCheck ? "on" : "off"})` },
|
|
3013
3042
|
{ value: "toggleserver", label: `Toggle server-version check: ${config.serverVersionCheck ? "on → off" : "off → on"}`, description: `serverVersionCheck (currently ${config.serverVersionCheck ? "on" : "off"}) — extension ↔ server sync, the startup warning` },
|
|
3014
|
-
{ value: "toggleupstream", label: `Toggle upstream-release notice: ${config.upstreamVersionCheck ? "on → off" : "off → on"}`, description: `upstreamVersionCheck (currently ${config.upstreamVersionCheck ? "on" : "off"}) — "a server update is available", info tier` },
|
|
3043
|
+
{ value: "toggleupstream", label: `Toggle upstream-release notice: ${config.upstreamVersionCheck ? "on → off" : "off → on"}`, description: `upstreamVersionCheck (currently ${config.upstreamVersionCheck ? "on" : "off"}) — "a server update is available", info tier, once per release` },
|
|
3015
3044
|
{ value: "back", label: "Back", description: "" },
|
|
3016
3045
|
]);
|
|
3017
3046
|
if (sub === null || sub === "back") break;
|
|
@@ -3062,7 +3091,9 @@ function registerSetupCommand(pi: ExtensionAPI, configRef: { current: LemonadeCo
|
|
|
3062
3091
|
"nothing at runtime. Re-sync re-downloads the current upstream docs into",
|
|
3063
3092
|
"the extension's docs/ folder; it does NOT update the extension code. The",
|
|
3064
3093
|
"startup warnings are reserved for extension ↔ server sync (warning) and",
|
|
3065
|
-
"server-vs-latest-release (one-line info notice
|
|
3094
|
+
"server-vs-latest-release (one-line info notice, deduplicated: it fires",
|
|
3095
|
+
"once per new release per instance — this view always shows the current",
|
|
3096
|
+
"state, even when the notice has already fired).",
|
|
3066
3097
|
],
|
|
3067
3098
|
true
|
|
3068
3099
|
);
|
package/lemonade.example.json
CHANGED
|
@@ -102,6 +102,8 @@
|
|
|
102
102
|
|
|
103
103
|
// Tier 2 (info notice, one sentence): servers older than the latest
|
|
104
104
|
// published lemonade release (probed via GitHub, throttled) get an
|
|
105
|
-
// "a server update is available" notice
|
|
105
|
+
// "a server update is available" notice — deduplicated to once per
|
|
106
|
+
// (instance, release); the /lemonade-setup TUI always shows the current
|
|
107
|
+
// state. Separately toggleable.
|
|
106
108
|
"upstreamVersionCheck": true
|
|
107
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lemonade-link",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Unified pi extension for a self-hosted Lemonade server: dynamic chat-model discovery, multimodal agent tools (transcription, image gen/edit/upscale, TTS, audio, 3D mesh), a live /lemonade-setup management TUI, and a lemonade-only below-editor status bar (tok/s, cache hits, busy/queue, VRAM).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|