dsh-workbuddy-connect 0.6.2 → 0.6.4
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.en.md +1 -0
- package/README.md +1 -0
- package/lib/bin.js +5 -7
- package/lib/client.js +845 -38
- package/lib/{variants-B3Pa4EBr.js → host-heartbeat-D9DvyLWz.js} +459 -133
- package/lib/index.d.ts +212 -142
- package/lib/index.js +313 -8
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -30,6 +30,14 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
*/
|
|
31
31
|
const WORKBUDDY_AI_STATUS_PATH = "/plugins/dsh-workbuddy-connect/ai/status";
|
|
32
32
|
const WORKBUDDY_AI_PROBE_PATH = "/plugins/dsh-workbuddy-connect/ai/probe";
|
|
33
|
+
/**
|
|
34
|
+
* Same-origin route backing the browser's update reminder.
|
|
35
|
+
*
|
|
36
|
+
* Read-only like the status route, so the same loopback Host/Origin gate
|
|
37
|
+
* applies; it answers public npm/GitHub metadata only and never token
|
|
38
|
+
* material.
|
|
39
|
+
*/
|
|
40
|
+
const WORKBUDDY_UPDATE_PATH = "/plugins/dsh-workbuddy-connect/update";
|
|
33
41
|
/** Every reason code, for validation without trusting a wire value. */
|
|
34
42
|
const SIGNED_OUT_REASON_CODES = [
|
|
35
43
|
"no-credential",
|
|
@@ -163,7 +171,7 @@ window.__ModuleLoader__.load({
|
|
|
163
171
|
children: t("assistantHeading")
|
|
164
172
|
}),
|
|
165
173
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
166
|
-
style: bodyStyle,
|
|
174
|
+
style: bodyStyle$1,
|
|
167
175
|
children: t("assistantIntro")
|
|
168
176
|
}),
|
|
169
177
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -173,7 +181,7 @@ window.__ModuleLoader__.load({
|
|
|
173
181
|
children: prompt
|
|
174
182
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
175
183
|
type: "button",
|
|
176
|
-
style: buttonStyle$
|
|
184
|
+
style: buttonStyle$2,
|
|
177
185
|
onClick: () => {
|
|
178
186
|
(async () => {
|
|
179
187
|
const ok = await copyPrompt(prompt);
|
|
@@ -195,14 +203,14 @@ window.__ModuleLoader__.load({
|
|
|
195
203
|
children: t("assistantCopyFailed")
|
|
196
204
|
}) : null,
|
|
197
205
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
198
|
-
style: bodyStyle,
|
|
206
|
+
style: bodyStyle$1,
|
|
199
207
|
children: t("assistantAfter")
|
|
200
208
|
}),
|
|
201
209
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
202
|
-
style: rowStyle,
|
|
210
|
+
style: rowStyle$1,
|
|
203
211
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
204
212
|
type: "button",
|
|
205
|
-
style: buttonStyle$
|
|
213
|
+
style: buttonStyle$2,
|
|
206
214
|
disabled: busy,
|
|
207
215
|
onClick: onRecheck,
|
|
208
216
|
children: busy ? t("assistantRechecking") : t("assistantRecheck")
|
|
@@ -259,13 +267,13 @@ window.__ModuleLoader__.load({
|
|
|
259
267
|
borderTop: "1px solid var(--dsw-alias-border-l2)",
|
|
260
268
|
padding: "16px 14px 18px"
|
|
261
269
|
};
|
|
262
|
-
const bodyStyle = {
|
|
270
|
+
const bodyStyle$1 = {
|
|
263
271
|
margin: 0,
|
|
264
272
|
fontSize: 14,
|
|
265
273
|
lineHeight: "22px",
|
|
266
274
|
color: "var(--dsw-alias-label-secondary)"
|
|
267
275
|
};
|
|
268
|
-
const rowStyle = {
|
|
276
|
+
const rowStyle$1 = {
|
|
269
277
|
display: "flex",
|
|
270
278
|
alignItems: "center",
|
|
271
279
|
justifyContent: "space-between",
|
|
@@ -280,7 +288,7 @@ window.__ModuleLoader__.load({
|
|
|
280
288
|
fontWeight: 500,
|
|
281
289
|
color: "var(--dsw-alias-label-primary)"
|
|
282
290
|
};
|
|
283
|
-
const buttonStyle$
|
|
291
|
+
const buttonStyle$2 = {
|
|
284
292
|
boxSizing: "border-box",
|
|
285
293
|
minHeight: 34,
|
|
286
294
|
padding: "6px 14px",
|
|
@@ -293,7 +301,7 @@ window.__ModuleLoader__.load({
|
|
|
293
301
|
cursor: "pointer"
|
|
294
302
|
};
|
|
295
303
|
const errorStyle = {
|
|
296
|
-
...bodyStyle,
|
|
304
|
+
...bodyStyle$1,
|
|
297
305
|
color: "var(--dsw-alias-state-error-primary)"
|
|
298
306
|
};
|
|
299
307
|
const quotaListStyle = {
|
|
@@ -511,8 +519,8 @@ window.__ModuleLoader__.load({
|
|
|
511
519
|
* theme as a pair: `brand-primary` is a light accent here, so pairing it with a
|
|
512
520
|
* hardcoded white would render white-on-white.
|
|
513
521
|
*/
|
|
514
|
-
const primaryButtonStyle$
|
|
515
|
-
...buttonStyle$
|
|
522
|
+
const primaryButtonStyle$2 = {
|
|
523
|
+
...buttonStyle$2,
|
|
516
524
|
border: "1px solid var(--dsw-alias-button-primary-fill)",
|
|
517
525
|
background: "var(--dsw-alias-button-primary-fill)",
|
|
518
526
|
color: "var(--dsw-alias-label-primary-foreground)"
|
|
@@ -580,7 +588,7 @@ window.__ModuleLoader__.load({
|
|
|
580
588
|
"aria-valuetext": quotaText
|
|
581
589
|
}),
|
|
582
590
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
583
|
-
style: bodyStyle,
|
|
591
|
+
style: bodyStyle$1,
|
|
584
592
|
children: quotaText
|
|
585
593
|
})
|
|
586
594
|
]
|
|
@@ -612,7 +620,7 @@ window.__ModuleLoader__.load({
|
|
|
612
620
|
children: percent === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: progressFillStyle(percent) })
|
|
613
621
|
}),
|
|
614
622
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
615
|
-
style: bodyStyle,
|
|
623
|
+
style: bodyStyle$1,
|
|
616
624
|
children: detail
|
|
617
625
|
})
|
|
618
626
|
]
|
|
@@ -710,7 +718,7 @@ window.__ModuleLoader__.load({
|
|
|
710
718
|
})]
|
|
711
719
|
}) : null,
|
|
712
720
|
visibility === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
713
|
-
style: bodyStyle,
|
|
721
|
+
style: bodyStyle$1,
|
|
714
722
|
children: t("visibilityIntro")
|
|
715
723
|
}),
|
|
716
724
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -822,19 +830,19 @@ window.__ModuleLoader__.load({
|
|
|
822
830
|
children: t("probeHeading")
|
|
823
831
|
}),
|
|
824
832
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
825
|
-
style: bodyStyle,
|
|
833
|
+
style: bodyStyle$1,
|
|
826
834
|
children: t("probeIntro")
|
|
827
835
|
}),
|
|
828
836
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
829
|
-
style: bodyStyle,
|
|
837
|
+
style: bodyStyle$1,
|
|
830
838
|
children: t("probeConsentHint")
|
|
831
839
|
}),
|
|
832
840
|
probe.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
833
|
-
style: bodyStyle,
|
|
841
|
+
style: bodyStyle$1,
|
|
834
842
|
children: t("probeRunningGeneric")
|
|
835
843
|
}) : null,
|
|
836
844
|
probe.candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
837
|
-
style: bodyStyle,
|
|
845
|
+
style: bodyStyle$1,
|
|
838
846
|
children: t("probeResultEmpty")
|
|
839
847
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
840
848
|
style: quotaGroupStyle,
|
|
@@ -853,7 +861,7 @@ window.__ModuleLoader__.load({
|
|
|
853
861
|
children: result.validation === "validating" && result.efforts.length > 0 ? result.efforts.join(" / ") : t(result.validation === "non-validating" ? "probeResultNotValidating" : "probeResultUnknown")
|
|
854
862
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
855
863
|
type: "button",
|
|
856
|
-
style: buttonStyle$
|
|
864
|
+
style: buttonStyle$2,
|
|
857
865
|
disabled: probe.running || busy,
|
|
858
866
|
onClick: () => {
|
|
859
867
|
setPending(id);
|
|
@@ -869,20 +877,20 @@ window.__ModuleLoader__.load({
|
|
|
869
877
|
pending === id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
870
878
|
style: confirmBoxStyle,
|
|
871
879
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
872
|
-
style: bodyStyle,
|
|
880
|
+
style: bodyStyle$1,
|
|
873
881
|
children: t("probeConfirmBody", { model: name })
|
|
874
882
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
875
883
|
style: confirmRowStyle$1,
|
|
876
884
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
877
885
|
type: "button",
|
|
878
|
-
style: buttonStyle$
|
|
886
|
+
style: buttonStyle$2,
|
|
879
887
|
onClick: () => {
|
|
880
888
|
setPending(void 0);
|
|
881
889
|
},
|
|
882
890
|
children: t("cancel")
|
|
883
891
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
884
892
|
type: "button",
|
|
885
|
-
style: primaryButtonStyle$
|
|
893
|
+
style: primaryButtonStyle$2,
|
|
886
894
|
disabled: probe.running || busy,
|
|
887
895
|
onClick: () => {
|
|
888
896
|
setRunningModel(id);
|
|
@@ -899,7 +907,7 @@ window.__ModuleLoader__.load({
|
|
|
899
907
|
}),
|
|
900
908
|
probe.results.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
901
909
|
type: "button",
|
|
902
|
-
style: buttonStyle$
|
|
910
|
+
style: buttonStyle$2,
|
|
903
911
|
disabled: busy,
|
|
904
912
|
onClick: () => {
|
|
905
913
|
onClear();
|
|
@@ -1207,7 +1215,7 @@ window.__ModuleLoader__.load({
|
|
|
1207
1215
|
children: t("accountHeading")
|
|
1208
1216
|
}),
|
|
1209
1217
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1210
|
-
style: rowStyle,
|
|
1218
|
+
style: rowStyle$1,
|
|
1211
1219
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1212
1220
|
style: statusStyle,
|
|
1213
1221
|
role: "status",
|
|
@@ -1218,7 +1226,7 @@ window.__ModuleLoader__.load({
|
|
|
1218
1226
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
1219
1227
|
}), assistCode === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1220
1228
|
type: "button",
|
|
1221
|
-
style: buttonStyle$
|
|
1229
|
+
style: buttonStyle$2,
|
|
1222
1230
|
disabled: busy,
|
|
1223
1231
|
onClick: () => {
|
|
1224
1232
|
manualRefresh();
|
|
@@ -1232,17 +1240,17 @@ window.__ModuleLoader__.load({
|
|
|
1232
1240
|
}),
|
|
1233
1241
|
status?.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1234
1242
|
status.expiresAt === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1235
|
-
style: bodyStyle,
|
|
1243
|
+
style: bodyStyle$1,
|
|
1236
1244
|
children: t("accessTokenExpires", { time: formatTime(status.expiresAt) })
|
|
1237
1245
|
}),
|
|
1238
1246
|
status.catalog === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1239
|
-
style: rowStyle,
|
|
1247
|
+
style: rowStyle$1,
|
|
1240
1248
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1241
|
-
style: bodyStyle,
|
|
1249
|
+
style: bodyStyle$1,
|
|
1242
1250
|
children: [status.catalog.source === "live" && status.catalog.fetchedAt !== void 0 ? t("catalogLive", { time: formatTime(status.catalog.fetchedAt) }) : status.catalog.source === "saved" && status.catalog.fetchedAt !== void 0 ? t("catalogSaved", { time: formatTime(status.catalog.fetchedAt) }) : t("catalogFallback"), status.catalog.appVersion === void 0 ? "" : ` · ${t("catalogAppVersion", { version: status.catalog.appVersion })}`]
|
|
1243
1251
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1244
1252
|
type: "button",
|
|
1245
|
-
style: buttonStyle$
|
|
1253
|
+
style: buttonStyle$2,
|
|
1246
1254
|
disabled: busy,
|
|
1247
1255
|
onClick: () => {
|
|
1248
1256
|
refreshModels();
|
|
@@ -1281,12 +1289,12 @@ window.__ModuleLoader__.load({
|
|
|
1281
1289
|
status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1282
1290
|
style: quotaListStyle,
|
|
1283
1291
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1284
|
-
style: rowStyle,
|
|
1292
|
+
style: rowStyle$1,
|
|
1285
1293
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1286
1294
|
style: quotaTitleStyle,
|
|
1287
1295
|
children: t("creditsHeading")
|
|
1288
1296
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1289
|
-
style: bodyStyle,
|
|
1297
|
+
style: bodyStyle$1,
|
|
1290
1298
|
children: status.credits.unlimited === true ? t("creditsTotalUnlimited") : t("creditsTotal", { total: formatNumber(status.credits.total) })
|
|
1291
1299
|
})]
|
|
1292
1300
|
}), status.credits.cycleResetTime === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -1363,7 +1371,7 @@ window.__ModuleLoader__.load({
|
|
|
1363
1371
|
})
|
|
1364
1372
|
] }) : null,
|
|
1365
1373
|
status?.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1366
|
-
style: status.reason === void 0 ? bodyStyle : errorStyle,
|
|
1374
|
+
style: status.reason === void 0 ? bodyStyle$1 : errorStyle,
|
|
1367
1375
|
children: status.reason ?? t(variant.signedOutKey)
|
|
1368
1376
|
}), assistCode === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssistBlock, {
|
|
1369
1377
|
t,
|
|
@@ -1424,7 +1432,7 @@ window.__ModuleLoader__.load({
|
|
|
1424
1432
|
transform: "translateY(2px)",
|
|
1425
1433
|
marginRight: -8
|
|
1426
1434
|
};
|
|
1427
|
-
const buttonStyle = {
|
|
1435
|
+
const buttonStyle$1 = {
|
|
1428
1436
|
display: "inline-flex",
|
|
1429
1437
|
alignItems: "center",
|
|
1430
1438
|
justifyContent: "center",
|
|
@@ -1509,7 +1517,7 @@ window.__ModuleLoader__.load({
|
|
|
1509
1517
|
* theme's own pair for exactly this, and is what `dsh-codex-connect` uses for
|
|
1510
1518
|
* the same job.
|
|
1511
1519
|
*/
|
|
1512
|
-
const primaryButtonStyle = {
|
|
1520
|
+
const primaryButtonStyle$1 = {
|
|
1513
1521
|
...confirmButtonStyle,
|
|
1514
1522
|
border: "1px solid var(--dsw-alias-button-primary-fill)",
|
|
1515
1523
|
background: "var(--dsw-alias-button-primary-fill)",
|
|
@@ -1727,7 +1735,7 @@ window.__ModuleLoader__.load({
|
|
|
1727
1735
|
setTooltipVisible(false);
|
|
1728
1736
|
},
|
|
1729
1737
|
style: {
|
|
1730
|
-
...buttonStyle,
|
|
1738
|
+
...buttonStyle$1,
|
|
1731
1739
|
opacity: disabled && !confirming ? .6 : 1,
|
|
1732
1740
|
cursor: disabled ? "default" : "pointer"
|
|
1733
1741
|
},
|
|
@@ -1782,7 +1790,7 @@ window.__ModuleLoader__.load({
|
|
|
1782
1790
|
children: t("cancel")
|
|
1783
1791
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1784
1792
|
type: "button",
|
|
1785
|
-
style: primaryButtonStyle,
|
|
1793
|
+
style: primaryButtonStyle$1,
|
|
1786
1794
|
onClick: () => {
|
|
1787
1795
|
detect();
|
|
1788
1796
|
},
|
|
@@ -1813,6 +1821,748 @@ window.__ModuleLoader__.load({
|
|
|
1813
1821
|
return t("probeNoteUnknown");
|
|
1814
1822
|
}
|
|
1815
1823
|
//#endregion
|
|
1824
|
+
//#region src/update.ts
|
|
1825
|
+
/**
|
|
1826
|
+
* Public update metadata and bounded version checking for WorkBuddy Connect.
|
|
1827
|
+
*
|
|
1828
|
+
* Two upstreams, strictly layered so the judgement never depends on the
|
|
1829
|
+
* enrichment: npm's dist-tags decide whether a newer release exists, and only
|
|
1830
|
+
* then is GitHub's release list consulted — one request that feeds both the
|
|
1831
|
+
* "versions behind" count and the per-release notes. Every upstream answer is
|
|
1832
|
+
* treated as untrusted input: bodies are size-capped while streaming, releases
|
|
1833
|
+
* are re-validated on the browser side by {@link parseWorkBuddyUpdateResult}
|
|
1834
|
+
* before anything renders.
|
|
1835
|
+
*
|
|
1836
|
+
* This module is host/browser shared and dependency-free by design (the client
|
|
1837
|
+
* bundle's runtime imports stay React + local modules only).
|
|
1838
|
+
*
|
|
1839
|
+
* @module dsh-workbuddy-connect/update
|
|
1840
|
+
*/
|
|
1841
|
+
const WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
|
|
1842
|
+
`${WORKBUDDY_REPOSITORY_URL.replace("https://github.com", "https://api.github.com/repos")}`;
|
|
1843
|
+
const WORKBUDDY_RELEASE_PAGE_BASE = `${WORKBUDDY_REPOSITORY_URL}/releases/tag/`;
|
|
1844
|
+
/** GitHub caps one page at 100 releases; ours is far below that today. */
|
|
1845
|
+
const RELEASES_LIST_MAX = 100;
|
|
1846
|
+
const RELEASE_NAME_MAX_CHARS = 200;
|
|
1847
|
+
const RELEASE_NOTES_MAX_CHARS = 16e3;
|
|
1848
|
+
/** Parse one exact SemVer version, accepting the conventional leading `v`. */
|
|
1849
|
+
function parseWorkBuddyVersion(raw) {
|
|
1850
|
+
if (typeof raw !== "string") return void 0;
|
|
1851
|
+
const normalized = raw.startsWith("v") ? raw.slice(1) : raw;
|
|
1852
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(normalized);
|
|
1853
|
+
if (match === null) return void 0;
|
|
1854
|
+
const rawPrerelease = match[4] === void 0 ? [] : match[4].split(".");
|
|
1855
|
+
if (rawPrerelease.some((identifier) => /^\d+$/u.test(identifier) && !/^(0|[1-9]\d*)$/u.test(identifier))) return void 0;
|
|
1856
|
+
const prerelease = rawPrerelease.map((identifier) => /^(0|[1-9]\d*)$/u.test(identifier) ? Number(identifier) : identifier);
|
|
1857
|
+
if (prerelease.some((identifier) => typeof identifier === "number" && !Number.isSafeInteger(identifier))) return void 0;
|
|
1858
|
+
const parsed = {
|
|
1859
|
+
major: Number(match[1]),
|
|
1860
|
+
minor: Number(match[2]),
|
|
1861
|
+
patch: Number(match[3]),
|
|
1862
|
+
prerelease
|
|
1863
|
+
};
|
|
1864
|
+
return [
|
|
1865
|
+
parsed.major,
|
|
1866
|
+
parsed.minor,
|
|
1867
|
+
parsed.patch
|
|
1868
|
+
].every(Number.isSafeInteger) ? parsed : void 0;
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* One canonical spelling per SemVer value: leading `v` and build metadata
|
|
1872
|
+
* fold away, so `v0.6.4`, `0.6.4`, and `0.6.4+build` dedupe as one release.
|
|
1873
|
+
* Returns `undefined` for unparseable input; callers drop those first.
|
|
1874
|
+
*/
|
|
1875
|
+
function canonicalWorkBuddyVersion(version) {
|
|
1876
|
+
const parsed = parseWorkBuddyVersion(version);
|
|
1877
|
+
if (parsed === void 0) return void 0;
|
|
1878
|
+
return `${String(parsed.major)}.${String(parsed.minor)}.${String(parsed.patch)}` + (parsed.prerelease.length === 0 ? "" : `-${parsed.prerelease.join(".")}`);
|
|
1879
|
+
}
|
|
1880
|
+
function compareIdentifiers(left, right) {
|
|
1881
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
|
|
1882
|
+
if (typeof left === "number") return -1;
|
|
1883
|
+
if (typeof right === "number") return 1;
|
|
1884
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
1885
|
+
}
|
|
1886
|
+
/** Compare two versions using SemVer precedence (build metadata ignored). */
|
|
1887
|
+
function compareWorkBuddyVersions(left, right) {
|
|
1888
|
+
const a = parseWorkBuddyVersion(left);
|
|
1889
|
+
const b = parseWorkBuddyVersion(right);
|
|
1890
|
+
if (a === void 0 || b === void 0) throw new TypeError("invalid WorkBuddy Connect version");
|
|
1891
|
+
for (const [aPart, bPart] of [
|
|
1892
|
+
[a.major, b.major],
|
|
1893
|
+
[a.minor, b.minor],
|
|
1894
|
+
[a.patch, b.patch]
|
|
1895
|
+
]) if (aPart !== bPart) return aPart < bPart ? -1 : 1;
|
|
1896
|
+
if (a.prerelease.length === 0 && b.prerelease.length !== 0) return 1;
|
|
1897
|
+
if (a.prerelease.length !== 0 && b.prerelease.length === 0) return -1;
|
|
1898
|
+
for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
|
|
1899
|
+
const aPart = a.prerelease[index];
|
|
1900
|
+
const bPart = b.prerelease[index];
|
|
1901
|
+
if (aPart === void 0) return -1;
|
|
1902
|
+
if (bPart === void 0) return 1;
|
|
1903
|
+
const comparison = compareIdentifiers(aPart, bPart);
|
|
1904
|
+
if (comparison !== 0) return comparison;
|
|
1905
|
+
}
|
|
1906
|
+
return 0;
|
|
1907
|
+
}
|
|
1908
|
+
/** Strip control characters, normalize newlines, and cap the length. */
|
|
1909
|
+
function cleanReleaseText(value, maxLength) {
|
|
1910
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
1911
|
+
const clean = value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/gu, "").replace(/\r\n?/gu, "\n").trim().slice(0, maxLength);
|
|
1912
|
+
return clean.length === 0 ? void 0 : clean;
|
|
1913
|
+
}
|
|
1914
|
+
function cleanPublishedAt(value) {
|
|
1915
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/iu.test(value) ? value.slice(0, 64) : void 0;
|
|
1916
|
+
}
|
|
1917
|
+
function releasePageUrl(version) {
|
|
1918
|
+
return `${WORKBUDDY_RELEASE_PAGE_BASE}v${version}`;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Validate a route response before the browser renders it. Same distrust as
|
|
1922
|
+
* the host side: the versions must parse, `update-available` must still hold
|
|
1923
|
+
* under a fresh comparison, the release URL must equal the one derived from
|
|
1924
|
+
* the version, and every listed release must fall in (current, latest] — a
|
|
1925
|
+
* host that invents content fails closed here.
|
|
1926
|
+
*/
|
|
1927
|
+
function parseWorkBuddyUpdateResult(value) {
|
|
1928
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1929
|
+
const record = value;
|
|
1930
|
+
const currentVersion = record["currentVersion"];
|
|
1931
|
+
if (typeof currentVersion !== "string" || parseWorkBuddyVersion(currentVersion) === void 0) return void 0;
|
|
1932
|
+
if (record["status"] === "unavailable") {
|
|
1933
|
+
const reason = record["reason"];
|
|
1934
|
+
return reason === "invalid-current-version" || reason === "registry-unavailable" || reason === "invalid-registry-response" ? {
|
|
1935
|
+
status: "unavailable",
|
|
1936
|
+
currentVersion,
|
|
1937
|
+
reason
|
|
1938
|
+
} : void 0;
|
|
1939
|
+
}
|
|
1940
|
+
const latestVersion = record["latestVersion"];
|
|
1941
|
+
if (typeof latestVersion !== "string" || parseWorkBuddyVersion(latestVersion) === void 0) return void 0;
|
|
1942
|
+
if (record["status"] === "up-to-date") return {
|
|
1943
|
+
status: "up-to-date",
|
|
1944
|
+
currentVersion,
|
|
1945
|
+
latestVersion
|
|
1946
|
+
};
|
|
1947
|
+
if (record["status"] !== "update-available" || compareWorkBuddyVersions(latestVersion, currentVersion) <= 0) return void 0;
|
|
1948
|
+
const expectedUrl = releasePageUrl(latestVersion);
|
|
1949
|
+
if (record["releaseUrl"] !== expectedUrl) return void 0;
|
|
1950
|
+
if (!Array.isArray(record["releases"]) || record["releases"].length > RELEASES_LIST_MAX) return void 0;
|
|
1951
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1952
|
+
const releases = [];
|
|
1953
|
+
for (const raw of record["releases"]) {
|
|
1954
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
1955
|
+
const entry = raw;
|
|
1956
|
+
const version = entry["version"];
|
|
1957
|
+
if (typeof version !== "string" || parseWorkBuddyVersion(version) === void 0) return void 0;
|
|
1958
|
+
if (compareWorkBuddyVersions(version, currentVersion) <= 0 || compareWorkBuddyVersions(version, latestVersion) > 0) return void 0;
|
|
1959
|
+
const canonical = canonicalWorkBuddyVersion(version);
|
|
1960
|
+
if (canonical === void 0 || seen.has(canonical)) return void 0;
|
|
1961
|
+
seen.add(canonical);
|
|
1962
|
+
const name = cleanReleaseText(entry["name"], RELEASE_NAME_MAX_CHARS);
|
|
1963
|
+
const notes = cleanReleaseText(entry["notes"], RELEASE_NOTES_MAX_CHARS);
|
|
1964
|
+
const publishedAt = cleanPublishedAt(entry["publishedAt"]);
|
|
1965
|
+
releases.push({
|
|
1966
|
+
version,
|
|
1967
|
+
...name === void 0 ? {} : { name },
|
|
1968
|
+
...notes === void 0 ? {} : { notes },
|
|
1969
|
+
...publishedAt === void 0 ? {} : { publishedAt }
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
const rawVersionsBehind = record["versionsBehind"];
|
|
1973
|
+
if (rawVersionsBehind !== void 0 && (typeof rawVersionsBehind !== "number" || !Number.isSafeInteger(rawVersionsBehind) || rawVersionsBehind !== releases.length)) return void 0;
|
|
1974
|
+
return {
|
|
1975
|
+
status: "update-available",
|
|
1976
|
+
currentVersion,
|
|
1977
|
+
latestVersion,
|
|
1978
|
+
releaseUrl: expectedUrl,
|
|
1979
|
+
releases,
|
|
1980
|
+
...rawVersionsBehind === void 0 ? {} : { versionsBehind: rawVersionsBehind }
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
//#endregion
|
|
1984
|
+
//#region src/client/WorkBuddyUpdateNotice.tsx
|
|
1985
|
+
/**
|
|
1986
|
+
* The bottom-right update reminder: one floating panel, rendered only while a
|
|
1987
|
+
* newer release exists and has not been dismissed. Each in-range release
|
|
1988
|
+
* shows as a collapsed title row (our release titles are written as bilingual
|
|
1989
|
+
* user-facing summaries, so the row doubles as the highlight line); opening
|
|
1990
|
+
* one reveals its notes under the markdown whitelist below.
|
|
1991
|
+
*/
|
|
1992
|
+
const DEFAULT_T = (key, _params) => String(key);
|
|
1993
|
+
const panelStyle = {
|
|
1994
|
+
display: "flex",
|
|
1995
|
+
flexDirection: "column",
|
|
1996
|
+
gap: 10,
|
|
1997
|
+
padding: "13px 15px",
|
|
1998
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1999
|
+
borderRadius: 12,
|
|
2000
|
+
background: "var(--dsw-alias-bg-module-platform)",
|
|
2001
|
+
color: "var(--dsw-alias-label-primary)"
|
|
2002
|
+
};
|
|
2003
|
+
const overlayStyle = {
|
|
2004
|
+
position: "fixed",
|
|
2005
|
+
bottom: 16,
|
|
2006
|
+
right: 20,
|
|
2007
|
+
zIndex: 30,
|
|
2008
|
+
width: "min(440px, calc(100vw - 40px))",
|
|
2009
|
+
pointerEvents: "auto",
|
|
2010
|
+
maxHeight: "calc(100vh - 32px)",
|
|
2011
|
+
overflowY: "auto",
|
|
2012
|
+
boxSizing: "border-box",
|
|
2013
|
+
boxShadow: "0 8px 28px rgba(0, 0, 0, 0.16)"
|
|
2014
|
+
};
|
|
2015
|
+
const rowStyle = {
|
|
2016
|
+
display: "flex",
|
|
2017
|
+
alignItems: "center",
|
|
2018
|
+
justifyContent: "space-between",
|
|
2019
|
+
gap: 12,
|
|
2020
|
+
flexWrap: "wrap"
|
|
2021
|
+
};
|
|
2022
|
+
const titleStyle = {
|
|
2023
|
+
margin: 0,
|
|
2024
|
+
fontSize: 14,
|
|
2025
|
+
lineHeight: "20px",
|
|
2026
|
+
fontWeight: 600
|
|
2027
|
+
};
|
|
2028
|
+
const bodyStyle = {
|
|
2029
|
+
margin: 0,
|
|
2030
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
2031
|
+
fontSize: 13,
|
|
2032
|
+
lineHeight: "20px"
|
|
2033
|
+
};
|
|
2034
|
+
const sectionStyle = {
|
|
2035
|
+
display: "flex",
|
|
2036
|
+
flexDirection: "column",
|
|
2037
|
+
gap: 6
|
|
2038
|
+
};
|
|
2039
|
+
const releaseRowStyle = {
|
|
2040
|
+
display: "flex",
|
|
2041
|
+
alignItems: "flex-start",
|
|
2042
|
+
gap: 8,
|
|
2043
|
+
padding: "7px 9px",
|
|
2044
|
+
borderRadius: 7,
|
|
2045
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04))",
|
|
2046
|
+
border: 0,
|
|
2047
|
+
width: "100%",
|
|
2048
|
+
boxSizing: "border-box",
|
|
2049
|
+
color: "inherit",
|
|
2050
|
+
font: "inherit",
|
|
2051
|
+
fontSize: 13,
|
|
2052
|
+
lineHeight: "19px",
|
|
2053
|
+
textAlign: "left",
|
|
2054
|
+
cursor: "pointer",
|
|
2055
|
+
overflowWrap: "anywhere"
|
|
2056
|
+
};
|
|
2057
|
+
const buttonStyle = {
|
|
2058
|
+
display: "inline-flex",
|
|
2059
|
+
alignItems: "center",
|
|
2060
|
+
justifyContent: "center",
|
|
2061
|
+
boxSizing: "border-box",
|
|
2062
|
+
minHeight: 32,
|
|
2063
|
+
padding: "4px 11px",
|
|
2064
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
2065
|
+
borderRadius: 7,
|
|
2066
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
2067
|
+
color: "var(--dsw-alias-label-primary)",
|
|
2068
|
+
font: "inherit",
|
|
2069
|
+
fontSize: 12,
|
|
2070
|
+
lineHeight: "20px",
|
|
2071
|
+
whiteSpace: "nowrap",
|
|
2072
|
+
cursor: "pointer"
|
|
2073
|
+
};
|
|
2074
|
+
const primaryButtonStyle = {
|
|
2075
|
+
...buttonStyle,
|
|
2076
|
+
borderColor: "var(--dsw-alias-button-primary-fill)",
|
|
2077
|
+
background: "var(--dsw-alias-button-primary-fill)",
|
|
2078
|
+
color: "var(--dsw-alias-label-primary-foreground)"
|
|
2079
|
+
};
|
|
2080
|
+
const textButtonStyle = {
|
|
2081
|
+
border: 0,
|
|
2082
|
+
padding: 0,
|
|
2083
|
+
background: "transparent",
|
|
2084
|
+
color: "var(--dsw-alias-brand-primary)",
|
|
2085
|
+
font: "inherit",
|
|
2086
|
+
fontSize: 12,
|
|
2087
|
+
lineHeight: "20px",
|
|
2088
|
+
cursor: "pointer",
|
|
2089
|
+
textDecoration: "underline",
|
|
2090
|
+
textUnderlineOffset: 2
|
|
2091
|
+
};
|
|
2092
|
+
const promptRowStyle = {
|
|
2093
|
+
display: "flex",
|
|
2094
|
+
alignItems: "center",
|
|
2095
|
+
gap: 8,
|
|
2096
|
+
padding: "7px 8px 7px 10px",
|
|
2097
|
+
borderRadius: 7,
|
|
2098
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.06))"
|
|
2099
|
+
};
|
|
2100
|
+
const promptTextStyle = {
|
|
2101
|
+
flex: "1 1 auto",
|
|
2102
|
+
minWidth: 0,
|
|
2103
|
+
margin: 0,
|
|
2104
|
+
padding: 0,
|
|
2105
|
+
background: "transparent",
|
|
2106
|
+
color: "var(--dsw-alias-label-primary)",
|
|
2107
|
+
fontSize: 12,
|
|
2108
|
+
lineHeight: "19px",
|
|
2109
|
+
whiteSpace: "pre-wrap",
|
|
2110
|
+
overflowWrap: "anywhere"
|
|
2111
|
+
};
|
|
2112
|
+
const notesStyle = {
|
|
2113
|
+
maxHeight: 220,
|
|
2114
|
+
overflowY: "auto",
|
|
2115
|
+
margin: "4px 0 0",
|
|
2116
|
+
padding: "9px 10px",
|
|
2117
|
+
borderRadius: 7,
|
|
2118
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.06))",
|
|
2119
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
2120
|
+
fontSize: 12,
|
|
2121
|
+
lineHeight: "19px",
|
|
2122
|
+
overflowWrap: "anywhere"
|
|
2123
|
+
};
|
|
2124
|
+
const notesListStyle = {
|
|
2125
|
+
margin: "4px 0",
|
|
2126
|
+
paddingLeft: 18
|
|
2127
|
+
};
|
|
2128
|
+
const notesHeadingStyle = {
|
|
2129
|
+
margin: "0 0 4px",
|
|
2130
|
+
fontSize: 12,
|
|
2131
|
+
lineHeight: "19px",
|
|
2132
|
+
fontWeight: 600,
|
|
2133
|
+
color: "var(--dsw-alias-label-primary)"
|
|
2134
|
+
};
|
|
2135
|
+
async function copyAgentPrompt(prompt) {
|
|
2136
|
+
try {
|
|
2137
|
+
if (navigator.clipboard?.writeText === void 0) return false;
|
|
2138
|
+
await navigator.clipboard.writeText(prompt);
|
|
2139
|
+
return true;
|
|
2140
|
+
} catch {
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
/** Only same-origin GitHub links may render as anchors; everything else is text. */
|
|
2145
|
+
function safeReleaseUrl(value) {
|
|
2146
|
+
try {
|
|
2147
|
+
const url = new URL(value);
|
|
2148
|
+
return url.protocol === "https:" && url.hostname === "github.com" ? url.href : void 0;
|
|
2149
|
+
} catch {
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
function renderInlineMarkdown(text, keyPrefix) {
|
|
2154
|
+
const tokens = /(?:\*\*[^*]+\*\*|\[[^\]]+\]\(https:\/\/[^)\s]+\)|https:\/\/[^\s<]+)/gu;
|
|
2155
|
+
const children = [];
|
|
2156
|
+
let lastIndex = 0;
|
|
2157
|
+
let match;
|
|
2158
|
+
let tokenIndex = 0;
|
|
2159
|
+
while ((match = tokens.exec(text)) !== null) {
|
|
2160
|
+
if (match.index > lastIndex) children.push(text.slice(lastIndex, match.index));
|
|
2161
|
+
const token = match[0];
|
|
2162
|
+
const bold = /^\*\*([^*]+)\*\*$/u.exec(token);
|
|
2163
|
+
const markdownLink = /^\[([^\]]+)\]\((https:\/\/[^)\s]+)\)$/u.exec(token);
|
|
2164
|
+
const bareUrl = /^https:\/\/[^\s<]+$/u.test(token) ? token.replace(/[.,]$/u, "") : void 0;
|
|
2165
|
+
if (bold !== null) children.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: bold[1] ?? "" }, `${keyPrefix}-bold-${tokenIndex}`));
|
|
2166
|
+
else if (markdownLink !== null) {
|
|
2167
|
+
const label = markdownLink[1] ?? token;
|
|
2168
|
+
const href = markdownLink[2] === void 0 ? void 0 : safeReleaseUrl(markdownLink[2]);
|
|
2169
|
+
children.push(href === void 0 ? label : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2170
|
+
href,
|
|
2171
|
+
target: "_blank",
|
|
2172
|
+
rel: "noopener noreferrer",
|
|
2173
|
+
children: label
|
|
2174
|
+
}, `${keyPrefix}-link-${tokenIndex}`));
|
|
2175
|
+
} else if (bareUrl !== void 0) {
|
|
2176
|
+
const href = safeReleaseUrl(bareUrl);
|
|
2177
|
+
children.push(href === void 0 ? token : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2178
|
+
href,
|
|
2179
|
+
target: "_blank",
|
|
2180
|
+
rel: "noopener noreferrer",
|
|
2181
|
+
children: token
|
|
2182
|
+
}, `${keyPrefix}-url-${tokenIndex}`));
|
|
2183
|
+
} else children.push(token);
|
|
2184
|
+
lastIndex = match.index + token.length;
|
|
2185
|
+
tokenIndex += 1;
|
|
2186
|
+
}
|
|
2187
|
+
if (lastIndex < text.length) children.push(text.slice(lastIndex));
|
|
2188
|
+
return children;
|
|
2189
|
+
}
|
|
2190
|
+
function renderReleaseNotes(markdown) {
|
|
2191
|
+
const content = [];
|
|
2192
|
+
let bullets = [];
|
|
2193
|
+
const flushBullets = () => {
|
|
2194
|
+
if (bullets.length === 0) return;
|
|
2195
|
+
content.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2196
|
+
style: notesListStyle,
|
|
2197
|
+
children: bullets
|
|
2198
|
+
}, `list-${content.length}`));
|
|
2199
|
+
bullets = [];
|
|
2200
|
+
};
|
|
2201
|
+
markdown.split("\n").forEach((line, index) => {
|
|
2202
|
+
const trimmed = line.trim();
|
|
2203
|
+
const bullet = /^[-*]\s+(.+)$/u.exec(trimmed);
|
|
2204
|
+
const heading = /^#{1,6}\s+(.+)$/u.exec(trimmed);
|
|
2205
|
+
if (bullet !== null) bullets.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: renderInlineMarkdown(bullet[1] ?? "", `item-${index}`) }, `item-${index}`));
|
|
2206
|
+
else if (heading !== null) {
|
|
2207
|
+
flushBullets();
|
|
2208
|
+
content.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
2209
|
+
style: notesHeadingStyle,
|
|
2210
|
+
children: renderInlineMarkdown(heading[1] ?? "", `heading-${index}`)
|
|
2211
|
+
}, `heading-${index}`));
|
|
2212
|
+
} else if (trimmed !== "") {
|
|
2213
|
+
flushBullets();
|
|
2214
|
+
content.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2215
|
+
style: {
|
|
2216
|
+
...bodyStyle,
|
|
2217
|
+
fontSize: 12,
|
|
2218
|
+
lineHeight: "19px"
|
|
2219
|
+
},
|
|
2220
|
+
children: renderInlineMarkdown(trimmed, `paragraph-${index}`)
|
|
2221
|
+
}, `paragraph-${index}`));
|
|
2222
|
+
} else flushBullets();
|
|
2223
|
+
});
|
|
2224
|
+
flushBullets();
|
|
2225
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2226
|
+
style: notesStyle,
|
|
2227
|
+
children: content
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
/** One in-range release: a collapsed title row that opens its notes. */
|
|
2231
|
+
function ReleaseRow({ release, t }) {
|
|
2232
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2233
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2234
|
+
type: "button",
|
|
2235
|
+
style: releaseRowStyle,
|
|
2236
|
+
"aria-expanded": open,
|
|
2237
|
+
onClick: () => {
|
|
2238
|
+
setOpen(!open);
|
|
2239
|
+
},
|
|
2240
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2241
|
+
"aria-hidden": true,
|
|
2242
|
+
children: open ? "▾" : "▸"
|
|
2243
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2244
|
+
style: {
|
|
2245
|
+
flex: "1 1 auto",
|
|
2246
|
+
minWidth: 0
|
|
2247
|
+
},
|
|
2248
|
+
children: release.name ?? `v${release.version}`
|
|
2249
|
+
})]
|
|
2250
|
+
}), open ? release.notes === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2251
|
+
style: {
|
|
2252
|
+
...bodyStyle,
|
|
2253
|
+
fontSize: 12,
|
|
2254
|
+
padding: "4px 9px 0",
|
|
2255
|
+
margin: 0
|
|
2256
|
+
},
|
|
2257
|
+
children: t("releaseNotesUnavailable")
|
|
2258
|
+
}) : renderReleaseNotes(release.notes) : null] });
|
|
2259
|
+
}
|
|
2260
|
+
/** Bottom-right reminder registered in the shell.overlay list seat. */
|
|
2261
|
+
function WorkBuddyUpdateOverlay({ t = DEFAULT_T, updater }) {
|
|
2262
|
+
if (updater === void 0) return null;
|
|
2263
|
+
const snapshot = (0, react.useSyncExternalStore)(updater.subscribe, updater.getSnapshot, updater.getSnapshot);
|
|
2264
|
+
const latestVersion = snapshot.latestVersion;
|
|
2265
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
2266
|
+
const [copyFailed, setCopyFailed] = (0, react.useState)(false);
|
|
2267
|
+
const [recheckRequested, setRecheckRequested] = (0, react.useState)(false);
|
|
2268
|
+
const noticeKey = latestVersion === void 0 ? void 0 : `${snapshot.currentVersion}:${latestVersion}`;
|
|
2269
|
+
if (!(recheckRequested && (snapshot.status === "checking" || snapshot.status === "unavailable")) && (snapshot.status !== "update-available" || noticeKey === void 0 || snapshot.dismissedNotice === noticeKey)) return null;
|
|
2270
|
+
const available = snapshot.status === "update-available";
|
|
2271
|
+
const releases = snapshot.releases ?? [];
|
|
2272
|
+
const agentPrompt = t("agentUpgradePrompt", { repository: WORKBUDDY_REPOSITORY_URL });
|
|
2273
|
+
const copy = async () => {
|
|
2274
|
+
setCopyFailed(false);
|
|
2275
|
+
const ok = await copyAgentPrompt(agentPrompt);
|
|
2276
|
+
setCopied(ok);
|
|
2277
|
+
setCopyFailed(!ok);
|
|
2278
|
+
};
|
|
2279
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2280
|
+
style: {
|
|
2281
|
+
...panelStyle,
|
|
2282
|
+
...overlayStyle
|
|
2283
|
+
},
|
|
2284
|
+
role: "status",
|
|
2285
|
+
"aria-label": t("updateNoticeLabel"),
|
|
2286
|
+
children: [
|
|
2287
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2288
|
+
style: rowStyle,
|
|
2289
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
2290
|
+
style: titleStyle,
|
|
2291
|
+
children: available ? t("newVersionAvailable", { version: latestVersion }) : t("updateNoticeLabel")
|
|
2292
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2293
|
+
type: "button",
|
|
2294
|
+
style: buttonStyle,
|
|
2295
|
+
"aria-label": t("dismissUpdate"),
|
|
2296
|
+
onClick: () => {
|
|
2297
|
+
setRecheckRequested(false);
|
|
2298
|
+
if (noticeKey !== void 0) updater.dismiss(noticeKey);
|
|
2299
|
+
},
|
|
2300
|
+
children: t("dismissUpdate")
|
|
2301
|
+
})]
|
|
2302
|
+
}),
|
|
2303
|
+
available ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2304
|
+
style: bodyStyle,
|
|
2305
|
+
children: [t("versionSummary", {
|
|
2306
|
+
current: snapshot.currentVersion,
|
|
2307
|
+
latest: latestVersion
|
|
2308
|
+
}), snapshot.versionsBehind === void 0 ? ` · ${t("versionsBehindUnknown")}` : ` · ${t("versionsBehind", { count: snapshot.versionsBehind })}`]
|
|
2309
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2310
|
+
style: sectionStyle,
|
|
2311
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
2312
|
+
style: titleStyle,
|
|
2313
|
+
children: t("releasesHeading")
|
|
2314
|
+
}), releases.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2315
|
+
style: bodyStyle,
|
|
2316
|
+
children: t("releaseListUnavailable")
|
|
2317
|
+
}) : releases.map((release) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReleaseRow, {
|
|
2318
|
+
release,
|
|
2319
|
+
t
|
|
2320
|
+
}, release.version))]
|
|
2321
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2322
|
+
style: bodyStyle,
|
|
2323
|
+
children: t("updateCheckUnavailable")
|
|
2324
|
+
}),
|
|
2325
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2326
|
+
style: sectionStyle,
|
|
2327
|
+
children: [
|
|
2328
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2329
|
+
style: promptRowStyle,
|
|
2330
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
2331
|
+
style: promptTextStyle,
|
|
2332
|
+
children: agentPrompt
|
|
2333
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2334
|
+
type: "button",
|
|
2335
|
+
style: buttonStyle,
|
|
2336
|
+
onClick: () => {
|
|
2337
|
+
copy();
|
|
2338
|
+
},
|
|
2339
|
+
children: copied ? t("agentPromptCopied") : t("copyForAgent")
|
|
2340
|
+
})]
|
|
2341
|
+
}),
|
|
2342
|
+
copyFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2343
|
+
style: {
|
|
2344
|
+
...bodyStyle,
|
|
2345
|
+
fontSize: 12,
|
|
2346
|
+
margin: 0
|
|
2347
|
+
},
|
|
2348
|
+
children: t("agentPromptCopyFailed")
|
|
2349
|
+
}) : null,
|
|
2350
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2351
|
+
style: rowStyle,
|
|
2352
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2353
|
+
type: "button",
|
|
2354
|
+
style: primaryButtonStyle,
|
|
2355
|
+
disabled: !available,
|
|
2356
|
+
onClick: () => {
|
|
2357
|
+
setRecheckRequested(true);
|
|
2358
|
+
updater.refresh(true);
|
|
2359
|
+
},
|
|
2360
|
+
children: snapshot.status === "checking" ? t("checkingForUpdates") : t("recheckAfterUpgrade")
|
|
2361
|
+
}), snapshot.releaseUrl === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2362
|
+
href: snapshot.releaseUrl,
|
|
2363
|
+
target: "_blank",
|
|
2364
|
+
rel: "noopener noreferrer",
|
|
2365
|
+
style: textButtonStyle,
|
|
2366
|
+
children: t("openReleasePage")
|
|
2367
|
+
})]
|
|
2368
|
+
}),
|
|
2369
|
+
recheckRequested && snapshot.status === "update-available" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2370
|
+
style: {
|
|
2371
|
+
...bodyStyle,
|
|
2372
|
+
fontSize: 12,
|
|
2373
|
+
margin: 0
|
|
2374
|
+
},
|
|
2375
|
+
children: t("upgradeStillAvailable", { version: snapshot.currentVersion })
|
|
2376
|
+
}) : null
|
|
2377
|
+
]
|
|
2378
|
+
})
|
|
2379
|
+
]
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
//#endregion
|
|
2383
|
+
//#region src/client/update-store.ts
|
|
2384
|
+
/**
|
|
2385
|
+
* Browser-owned cache and observable state for the update reminder.
|
|
2386
|
+
*
|
|
2387
|
+
* One reminder for the whole bundle — the check compares this npm package's
|
|
2388
|
+
* own version, so both provider cards share it. The cache is localStorage
|
|
2389
|
+
* with a 7-day TTL and the current version embedded, so an upgrade
|
|
2390
|
+
* invalidates it by itself. `unavailable` answers are never cached: the next
|
|
2391
|
+
* mount retries, and one in-page retry is scheduled after five minutes while
|
|
2392
|
+
* the page stays open.
|
|
2393
|
+
*/
|
|
2394
|
+
const WORKBUDDY_UPDATE_CACHE_KEY = "dsh-workbuddy-connect:update-check";
|
|
2395
|
+
const WORKBUDDY_UPDATE_DISMISSED_KEY = "dsh-workbuddy-connect:update-dismissed";
|
|
2396
|
+
/** Retry transient update-check failures while the page remains open. */
|
|
2397
|
+
const WORKBUDDY_UPDATE_RECHECK_MS = 3e5;
|
|
2398
|
+
/** The reminder's own fetch deadline, independent of the host's upstream one. */
|
|
2399
|
+
const ROUTE_TIMEOUT_MS = 3e4;
|
|
2400
|
+
function storage() {
|
|
2401
|
+
try {
|
|
2402
|
+
return typeof localStorage === "undefined" ? void 0 : localStorage;
|
|
2403
|
+
} catch {
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function resultSnapshot(result, dismissedNotice) {
|
|
2408
|
+
return {
|
|
2409
|
+
status: result.status,
|
|
2410
|
+
currentVersion: result.currentVersion,
|
|
2411
|
+
...result.status === "up-to-date" || result.status === "update-available" ? { latestVersion: result.latestVersion } : {},
|
|
2412
|
+
...result.status === "update-available" ? {
|
|
2413
|
+
releaseUrl: result.releaseUrl,
|
|
2414
|
+
releases: result.releases,
|
|
2415
|
+
...result.versionsBehind === void 0 ? {} : { versionsBehind: result.versionsBehind }
|
|
2416
|
+
} : {},
|
|
2417
|
+
...dismissedNotice === void 0 ? {} : { dismissedNotice }
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
/** Observable browser state behind the bottom-right reminder. */
|
|
2421
|
+
var WorkBuddyUpdateStore = class {
|
|
2422
|
+
currentVersion;
|
|
2423
|
+
snapshot;
|
|
2424
|
+
listeners = /* @__PURE__ */ new Set();
|
|
2425
|
+
request;
|
|
2426
|
+
disposed = false;
|
|
2427
|
+
recheckTimer;
|
|
2428
|
+
constructor(currentVersion) {
|
|
2429
|
+
this.currentVersion = currentVersion;
|
|
2430
|
+
this.snapshot = {
|
|
2431
|
+
status: "idle",
|
|
2432
|
+
currentVersion
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
getSnapshot = () => this.snapshot;
|
|
2436
|
+
subscribe = (listener) => {
|
|
2437
|
+
this.listeners.add(listener);
|
|
2438
|
+
return () => {
|
|
2439
|
+
this.listeners.delete(listener);
|
|
2440
|
+
};
|
|
2441
|
+
};
|
|
2442
|
+
setSnapshot(next) {
|
|
2443
|
+
if (this.disposed) return;
|
|
2444
|
+
this.snapshot = next;
|
|
2445
|
+
for (const listener of this.listeners) listener();
|
|
2446
|
+
}
|
|
2447
|
+
dismissedNotice() {
|
|
2448
|
+
try {
|
|
2449
|
+
const value = storage()?.getItem(WORKBUDDY_UPDATE_DISMISSED_KEY);
|
|
2450
|
+
return value === null || value === "" ? void 0 : value;
|
|
2451
|
+
} catch {
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
readCached() {
|
|
2456
|
+
try {
|
|
2457
|
+
const raw = storage()?.getItem(WORKBUDDY_UPDATE_CACHE_KEY);
|
|
2458
|
+
if (raw === null || raw === void 0) return void 0;
|
|
2459
|
+
const cached = JSON.parse(raw);
|
|
2460
|
+
if (!Number.isSafeInteger(cached.checkedAt) || cached.checkedAt > Date.now() || Date.now() - cached.checkedAt > 6048e5) return void 0;
|
|
2461
|
+
const result = parseWorkBuddyUpdateResult(cached.result);
|
|
2462
|
+
if (result === void 0 || result.status === "unavailable") return void 0;
|
|
2463
|
+
return {
|
|
2464
|
+
result,
|
|
2465
|
+
checkedAt: cached.checkedAt
|
|
2466
|
+
};
|
|
2467
|
+
} catch {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
writeCached(result, checkedAt) {
|
|
2472
|
+
try {
|
|
2473
|
+
if (result.status === "unavailable") storage()?.removeItem(WORKBUDDY_UPDATE_CACHE_KEY);
|
|
2474
|
+
else storage()?.setItem(WORKBUDDY_UPDATE_CACHE_KEY, JSON.stringify({
|
|
2475
|
+
checkedAt,
|
|
2476
|
+
result
|
|
2477
|
+
}));
|
|
2478
|
+
} catch {}
|
|
2479
|
+
}
|
|
2480
|
+
acceptResult(result) {
|
|
2481
|
+
if (this.disposed) return;
|
|
2482
|
+
const checkedAt = Date.now();
|
|
2483
|
+
this.writeCached(result, checkedAt);
|
|
2484
|
+
const next = resultSnapshot(result, this.dismissedNotice());
|
|
2485
|
+
if (result.status === "unavailable" && this.snapshot.latestVersion !== void 0) next.latestVersion = this.snapshot.latestVersion;
|
|
2486
|
+
this.setSnapshot({
|
|
2487
|
+
...next,
|
|
2488
|
+
checkedAt
|
|
2489
|
+
});
|
|
2490
|
+
if (result.status === "unavailable") this.recheckTimer = setTimeout(() => {
|
|
2491
|
+
this.refresh(true);
|
|
2492
|
+
}, WORKBUDDY_UPDATE_RECHECK_MS);
|
|
2493
|
+
}
|
|
2494
|
+
/** Reuse a result for a week; force bypasses the cache. */
|
|
2495
|
+
async refresh(force = false) {
|
|
2496
|
+
if (this.disposed || this.request !== void 0) return;
|
|
2497
|
+
clearTimeout(this.recheckTimer);
|
|
2498
|
+
this.recheckTimer = void 0;
|
|
2499
|
+
const controller = new AbortController();
|
|
2500
|
+
this.request = controller;
|
|
2501
|
+
const timer = setTimeout(() => {
|
|
2502
|
+
controller.abort(/* @__PURE__ */ new Error("update route timed out"));
|
|
2503
|
+
}, ROUTE_TIMEOUT_MS);
|
|
2504
|
+
this.setSnapshot({
|
|
2505
|
+
status: "checking",
|
|
2506
|
+
currentVersion: this.currentVersion,
|
|
2507
|
+
...this.snapshot.latestVersion === void 0 ? {} : { latestVersion: this.snapshot.latestVersion },
|
|
2508
|
+
...this.snapshot.dismissedNotice === void 0 ? {} : { dismissedNotice: this.snapshot.dismissedNotice }
|
|
2509
|
+
});
|
|
2510
|
+
try {
|
|
2511
|
+
if (!force) {
|
|
2512
|
+
const cached = this.readCached();
|
|
2513
|
+
if (cached !== void 0 && cached.result.currentVersion === this.currentVersion) {
|
|
2514
|
+
this.setSnapshot({
|
|
2515
|
+
...resultSnapshot(cached.result, this.dismissedNotice()),
|
|
2516
|
+
checkedAt: cached.checkedAt
|
|
2517
|
+
});
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
const response = await fetch(WORKBUDDY_UPDATE_PATH, {
|
|
2522
|
+
method: "GET",
|
|
2523
|
+
headers: { accept: "application/json" },
|
|
2524
|
+
credentials: "same-origin",
|
|
2525
|
+
signal: controller.signal
|
|
2526
|
+
});
|
|
2527
|
+
const value = await response.json().catch(() => void 0);
|
|
2528
|
+
const result = response.ok ? parseWorkBuddyUpdateResult(value) : void 0;
|
|
2529
|
+
this.acceptResult(result ?? {
|
|
2530
|
+
status: "unavailable",
|
|
2531
|
+
currentVersion: this.currentVersion,
|
|
2532
|
+
reason: "registry-unavailable"
|
|
2533
|
+
});
|
|
2534
|
+
} catch {
|
|
2535
|
+
if (!this.disposed) this.acceptResult({
|
|
2536
|
+
status: "unavailable",
|
|
2537
|
+
currentVersion: this.currentVersion,
|
|
2538
|
+
reason: "registry-unavailable"
|
|
2539
|
+
});
|
|
2540
|
+
} finally {
|
|
2541
|
+
clearTimeout(timer);
|
|
2542
|
+
if (this.request === controller) this.request = void 0;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
dismiss(notice) {
|
|
2546
|
+
try {
|
|
2547
|
+
storage()?.setItem(WORKBUDDY_UPDATE_DISMISSED_KEY, notice);
|
|
2548
|
+
} catch {}
|
|
2549
|
+
this.setSnapshot({
|
|
2550
|
+
...this.snapshot,
|
|
2551
|
+
dismissedNotice: notice
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
dispose() {
|
|
2555
|
+
this.disposed = true;
|
|
2556
|
+
clearTimeout(this.recheckTimer);
|
|
2557
|
+
this.request?.abort();
|
|
2558
|
+
this.request = void 0;
|
|
2559
|
+
this.listeners.clear();
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
//#endregion
|
|
2563
|
+
//#region src/version.ts
|
|
2564
|
+
const WORKBUDDY_CONNECT_VERSION = "0.6.4";
|
|
2565
|
+
//#endregion
|
|
1816
2566
|
//#region src/client/WorkBuddyConfigPage.tsx
|
|
1817
2567
|
/**
|
|
1818
2568
|
* The cards' list; the page supplies no other chrome. A semantic `<ul>` —
|
|
@@ -1952,7 +2702,25 @@ window.__ModuleLoader__.load({
|
|
|
1952
2702
|
assistIncomplete: "the automatic search could not be completed",
|
|
1953
2703
|
assistPathInvalid: "the configured program path is not usable",
|
|
1954
2704
|
assistUnavailableCN: "no decryption program is configured for this platform",
|
|
1955
|
-
assistUnavailableAI: "no decryption program is configured for WorkBuddy AI"
|
|
2705
|
+
assistUnavailableAI: "no decryption program is configured for WorkBuddy AI",
|
|
2706
|
+
updateNoticeLabel: "WorkBuddy Connect update",
|
|
2707
|
+
newVersionAvailable: "New version available: {version}",
|
|
2708
|
+
versionSummary: "Current {current} · Latest {latest}",
|
|
2709
|
+
versionsBehind: "{count} release(s) behind",
|
|
2710
|
+
versionsBehindUnknown: "release gap unavailable",
|
|
2711
|
+
releasesHeading: "What changed",
|
|
2712
|
+
releaseListUnavailable: "The per-release summary is unavailable right now; see the release page for details.",
|
|
2713
|
+
releaseNotesUnavailable: "Release notes for this version are unavailable.",
|
|
2714
|
+
copyForAgent: "Copy for agent",
|
|
2715
|
+
agentPromptCopied: "Copied",
|
|
2716
|
+
agentPromptCopyFailed: "Copy failed — select and copy the prompt manually.",
|
|
2717
|
+
recheckAfterUpgrade: "Re-check after upgrading",
|
|
2718
|
+
upgradeStillAvailable: "Upgrade not detected yet (still {version}). After upgrading, fully quit and restart DSH, then check again.",
|
|
2719
|
+
dismissUpdate: "Don't remind me again",
|
|
2720
|
+
openReleasePage: "Open release page",
|
|
2721
|
+
checkingForUpdates: "Checking…",
|
|
2722
|
+
updateCheckUnavailable: "Update information is unavailable right now. Check again later.",
|
|
2723
|
+
agentUpgradePrompt: "Please open {repository}, check its latest version, and install or update the plugin \"dsh-workbuddy-connect\" in my current DSH profile following the project README (the install command differs by profile). After upgrading, fully quit and restart DSH."
|
|
1956
2724
|
};
|
|
1957
2725
|
const zh = {
|
|
1958
2726
|
title: "DSH WorkBuddy Connect",
|
|
@@ -2053,7 +2821,25 @@ window.__ModuleLoader__.load({
|
|
|
2053
2821
|
assistIncomplete: "自动定位未能完成",
|
|
2054
2822
|
assistPathInvalid: "指定的程序路径不可用",
|
|
2055
2823
|
assistUnavailableCN: "当前平台尚未配置解密程序",
|
|
2056
|
-
assistUnavailableAI: "尚未配置 WorkBuddy AI 的解密程序"
|
|
2824
|
+
assistUnavailableAI: "尚未配置 WorkBuddy AI 的解密程序",
|
|
2825
|
+
updateNoticeLabel: "WorkBuddy Connect 更新",
|
|
2826
|
+
newVersionAvailable: "发现新版本:{version}",
|
|
2827
|
+
versionSummary: "当前 {current} · 最新 {latest}",
|
|
2828
|
+
versionsBehind: "落后 {count} 个版本",
|
|
2829
|
+
versionsBehindUnknown: "版本差距暂时不可知",
|
|
2830
|
+
releasesHeading: "更新内容",
|
|
2831
|
+
releaseListUnavailable: "各版本的更新说明暂时不可用,请到 Release 页面查看。",
|
|
2832
|
+
releaseNotesUnavailable: "该版本的发布说明不可用。",
|
|
2833
|
+
copyForAgent: "复制给 Agent",
|
|
2834
|
+
agentPromptCopied: "已复制",
|
|
2835
|
+
agentPromptCopyFailed: "复制失败,请手动选中复制。",
|
|
2836
|
+
recheckAfterUpgrade: "升级完成后,重新检查",
|
|
2837
|
+
upgradeStillAvailable: "暂未检测到升级(仍是 {version})。升级后请完全退出并重启 DSH 再检查。",
|
|
2838
|
+
dismissUpdate: "不再提醒",
|
|
2839
|
+
openReleasePage: "打开 Release 页面",
|
|
2840
|
+
checkingForUpdates: "检查中…",
|
|
2841
|
+
updateCheckUnavailable: "暂时无法获取更新信息,请稍后再试。",
|
|
2842
|
+
agentUpgradePrompt: "请打开 {repository},查看最新版本,并按项目 README 把插件 dsh-workbuddy-connect 安装或更新到我当前使用的 DSH profile(不同 profile 的安装命令不同)。升级完成后请完全退出并重启 DSH。"
|
|
2057
2843
|
};
|
|
2058
2844
|
//#endregion
|
|
2059
2845
|
//#region src/client/index.tsx
|
|
@@ -2147,6 +2933,27 @@ window.__ModuleLoader__.load({
|
|
|
2147
2933
|
}), "dsh-workbuddy-connect: settings copy");
|
|
2148
2934
|
});
|
|
2149
2935
|
const t = ctx.locale.bind(namespace);
|
|
2936
|
+
const updater = new WorkBuddyUpdateStore(WORKBUDDY_CONNECT_VERSION);
|
|
2937
|
+
guardClientContribution("update reminder lifecycle", () => {
|
|
2938
|
+
ctx.effect(() => {
|
|
2939
|
+
updater.refresh();
|
|
2940
|
+
return () => {
|
|
2941
|
+
updater.dispose();
|
|
2942
|
+
};
|
|
2943
|
+
}, "dsh-workbuddy-connect: update checker");
|
|
2944
|
+
});
|
|
2945
|
+
guardClientContribution("update reminder overlay", () => {
|
|
2946
|
+
ctx.slots.inject("shell.overlay", () => guardClientContribution("update reminder overlay", () => ctx.slots.register({
|
|
2947
|
+
name: "shell.overlay",
|
|
2948
|
+
id: "workbuddy-update",
|
|
2949
|
+
order: 40,
|
|
2950
|
+
locale: namespace,
|
|
2951
|
+
inject: () => ({
|
|
2952
|
+
t,
|
|
2953
|
+
updater
|
|
2954
|
+
})
|
|
2955
|
+
}, WorkBuddyUpdateOverlay)) ?? NOOP_DISPOSER);
|
|
2956
|
+
});
|
|
2150
2957
|
for (const [index, variant] of CARD_VARIANTS.entries()) {
|
|
2151
2958
|
const label = `settings.plugin.item card "${variant.id}"`;
|
|
2152
2959
|
guardClientContribution(label, () => {
|