dsh-connect-workbuddy 2.0.10 → 2.0.12
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/CHANGELOG.md +113 -4
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/lib/bin.js +1 -1
- package/lib/client.js +202 -69
- package/lib/{host-heartbeat-DPbL9oi2.js → host-heartbeat-ChtMu37E.js} +212 -18
- package/lib/index.d.ts +165 -8
- package/lib/index.js +151 -21
- package/package.json +4 -1
package/lib/client.js
CHANGED
|
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
9
|
//#region src/status-paths.ts
|
|
11
10
|
/**
|
|
@@ -130,16 +129,75 @@ window.__ModuleLoader__.load({
|
|
|
130
129
|
var WorkBuddySettingsWriteError = class extends Error {
|
|
131
130
|
/** The settings field that did not land. */
|
|
132
131
|
field;
|
|
133
|
-
constructor(field) {
|
|
134
|
-
super(`workbuddy: settings field "${field}" was not persisted by the settings write`);
|
|
132
|
+
constructor(field, reason) {
|
|
133
|
+
super(`workbuddy: settings field "${field}" was not persisted by the settings write${reason === void 0 ? "" : `: ${reason}`}`);
|
|
135
134
|
this.name = "WorkBuddySettingsWriteError";
|
|
136
135
|
this.field = field;
|
|
137
136
|
}
|
|
138
137
|
};
|
|
139
|
-
/** Read
|
|
140
|
-
function
|
|
141
|
-
const
|
|
142
|
-
return typeof
|
|
138
|
+
/** Read one settings field's current object value from the scope snapshot. */
|
|
139
|
+
function fieldSnapshotOf(scope, field) {
|
|
140
|
+
const value = scope.getSnapshot().value?.[field];
|
|
141
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Fallback write: POST the field to the plugin's own Host endpoint.
|
|
145
|
+
*
|
|
146
|
+
* The endpoint handler runs the settings mutate inside the Host process and
|
|
147
|
+
* reports the raw refusal exception, so a failure here names its cause instead
|
|
148
|
+
* of arriving as a swallowed `ok:false`. It merges the posted region key into
|
|
149
|
+
* the field's live value, preserving the other region.
|
|
150
|
+
*/
|
|
151
|
+
async function saveViaHostEndpoint(field, region, value) {
|
|
152
|
+
let response;
|
|
153
|
+
try {
|
|
154
|
+
response = await fetch("/plugins/dsh-connect-workbuddy/__save", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
field,
|
|
159
|
+
value: { [region]: value }
|
|
160
|
+
})
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw new WorkBuddySettingsWriteError(field, `Host save endpoint unreachable: ${String(error)}`);
|
|
164
|
+
}
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const detail = await response.json().catch(() => ({ error: `HTTP ${String(response.status)}` }));
|
|
167
|
+
const reason = `${String(detail.errorName ?? "")} ${String(detail.error ?? "")}`.trim();
|
|
168
|
+
throw new WorkBuddySettingsWriteError(field, `Host save refused: ${reason === "" ? String(detail.error) : reason}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Write one volatile field, then confirm the value actually landed.
|
|
173
|
+
*
|
|
174
|
+
* The bound settings scope is the official path and is tried FIRST: it keeps
|
|
175
|
+
* the browser mirror in sync, and it is the only writer needed on hosts whose
|
|
176
|
+
* ConfigForm is healthy. On the affected DSH 0.1.7 deployment that scope
|
|
177
|
+
* settles without delivering anything — its `set()` resolves `false` while the
|
|
178
|
+
* Host's own mutate is perfectly healthy (proven by a direct in-Host probe) —
|
|
179
|
+
* so a write that does not read back falls through to the plugin's Host
|
|
180
|
+
* endpoint, which performs the mutate inside the Host process.
|
|
181
|
+
*
|
|
182
|
+
* `landed` decides whether a value read back from the scope is the value that
|
|
183
|
+
* was written; it is per-field because `''` (a cleared account slot) and an
|
|
184
|
+
* absent key are different states, and a truncated model catalog must not pass
|
|
185
|
+
* a shallow "is anything there?" check.
|
|
186
|
+
*
|
|
187
|
+
* @throws {WorkBuddySettingsWriteError} when neither path persists the value.
|
|
188
|
+
*/
|
|
189
|
+
async function writeField(scope, field, region, value, landed) {
|
|
190
|
+
let scopeDelivered = false;
|
|
191
|
+
try {
|
|
192
|
+
scopeDelivered = await scope.set(field, {
|
|
193
|
+
...fieldSnapshotOf(scope, field),
|
|
194
|
+
[region]: value
|
|
195
|
+
}) !== false;
|
|
196
|
+
} catch {
|
|
197
|
+
scopeDelivered = false;
|
|
198
|
+
}
|
|
199
|
+
if (scopeDelivered && landed(fieldSnapshotOf(scope, field)[region])) return;
|
|
200
|
+
await saveViaHostEndpoint(field, region, value);
|
|
143
201
|
}
|
|
144
202
|
/**
|
|
145
203
|
* Write one region's account slot, then confirm the value actually landed.
|
|
@@ -168,12 +226,7 @@ window.__ModuleLoader__.load({
|
|
|
168
226
|
* something else — i.e. the write silently did not persist.
|
|
169
227
|
*/
|
|
170
228
|
async function writeAccountSlot(scope, region, value) {
|
|
171
|
-
|
|
172
|
-
await scope.set("accounts", {
|
|
173
|
-
...accounts,
|
|
174
|
-
[region]: value
|
|
175
|
-
});
|
|
176
|
-
if (configuredAccountsOf(scope.getSnapshot().value)[region] !== value) throw new WorkBuddySettingsWriteError("accounts");
|
|
229
|
+
await writeField(scope, "accounts", region, value, (readBack) => readBack === value);
|
|
177
230
|
}
|
|
178
231
|
/**
|
|
179
232
|
* Write one region's saved model state, then confirm it actually landed.
|
|
@@ -194,16 +247,43 @@ window.__ModuleLoader__.load({
|
|
|
194
247
|
* @throws {WorkBuddySettingsWriteError} when the catalog does not read back.
|
|
195
248
|
*/
|
|
196
249
|
async function writeRegionModels(scope, region, payload) {
|
|
197
|
-
const configured = scope.getSnapshot().value?.regions;
|
|
198
|
-
const regions = typeof configured === "object" && configured !== null ? configured : {};
|
|
199
|
-
await scope.set("regions", {
|
|
200
|
-
...regions,
|
|
201
|
-
[region]: payload
|
|
202
|
-
});
|
|
203
|
-
const landed = scope.getSnapshot().value?.regions?.[region];
|
|
204
250
|
const written = payload.lastCatalog.map((model) => model.id);
|
|
205
|
-
|
|
206
|
-
|
|
251
|
+
await writeField(scope, "regions", region, payload, (readBack) => {
|
|
252
|
+
const stored = readBack?.lastCatalog?.map((model) => model.id);
|
|
253
|
+
return stored !== void 0 && stored.length === written.length && stored.every((id, index) => id === written[index]);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Switch one region's provider off or on, with the same landed-check as every
|
|
258
|
+
* other settings write.
|
|
259
|
+
*
|
|
260
|
+
* This exists because the toggle was the ONE write that still called
|
|
261
|
+
* `scope.set()` directly. On the affected 0.1.7 deployments that scope settles
|
|
262
|
+
* without storing anything, so flipping a provider off appeared to work and
|
|
263
|
+
* then silently reverted — the exact silent-failure mode
|
|
264
|
+
* {@link writeAccountSlot} and {@link writeRegionModels} were written to
|
|
265
|
+
* prevent for their own fields. Routing it through {@link writeField} gives it
|
|
266
|
+
* the same scope-first-then-Host-endpoint path, so the switch either lands or
|
|
267
|
+
* reports why it did not.
|
|
268
|
+
*
|
|
269
|
+
* The write carries the region's whole slot through untouched — only `enabled`
|
|
270
|
+
* changes — so the user's directory, model picks, image opt-ins and budgets
|
|
271
|
+
* survive the round trip. The Host withdraws or restores the provider route on
|
|
272
|
+
* the next `onChange`, which is what actually removes it from DSH's model
|
|
273
|
+
* picker.
|
|
274
|
+
*
|
|
275
|
+
* @param scope - the bound settings scope for this plugin's namespace.
|
|
276
|
+
* @param region - the region being switched.
|
|
277
|
+
* @param enabled - the desired state.
|
|
278
|
+
* @param slot - the region's COMPLETE next slot (the caller owns the snapshot
|
|
279
|
+
* read and the whole-slot merge, so this stays a pure write).
|
|
280
|
+
* @throws {WorkBuddySettingsWriteError} when neither writer persists the value.
|
|
281
|
+
*/
|
|
282
|
+
async function writeRegionEnabled(scope, region, enabled, slot) {
|
|
283
|
+
await writeField(scope, "regions", region, slot, (readBack) => {
|
|
284
|
+
const stored = readBack;
|
|
285
|
+
return stored !== null && stored !== void 0 && stored["enabled"] === enabled;
|
|
286
|
+
});
|
|
207
287
|
}
|
|
208
288
|
//#endregion
|
|
209
289
|
//#region src/client/icon.ts
|
|
@@ -244,7 +324,11 @@ window.__ModuleLoader__.load({
|
|
|
244
324
|
.dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
245
325
|
.dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
|
|
246
326
|
.dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
|
|
247
|
-
|
|
327
|
+
/* Pure-CSS caret: the host primitives' chevron icon names differ per DSH line
|
|
328
|
+
(0.1.5 Outline14 vs 0.1.7 OutlineRegular), so no static import can serve
|
|
329
|
+
both. A border caret in the plugin's own CSS is version-proof. */
|
|
330
|
+
.dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;width:16px;height:16px;position:relative;transition:transform .16s}
|
|
331
|
+
.dsm-plugin-card-chevron::before{content:"";display:block;position:absolute;left:4px;top:5px;width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg)}
|
|
248
332
|
.dsm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
249
333
|
.dsm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2,#36373b);margin:0 16px;padding:0 0 8px}
|
|
250
334
|
.dsm-plugin-card-icon{width:32px;height:32px;flex:none;border-radius:7px}
|
|
@@ -291,7 +375,6 @@ window.__ModuleLoader__.load({
|
|
|
291
375
|
.dsm-workbuddy-searched-reason{color:var(--dsw-alias-label-tertiary,#999);font-size:11px}
|
|
292
376
|
.dsm-workbuddy-searched-reason-encrypted{color:var(--dsw-alias-state-warning-primary,#e0a13a)}
|
|
293
377
|
.dsm-workbuddy-searched-reason-wrong-region{color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
294
|
-
.dsm-workbuddy-searched-notice{margin:6px 0 0;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:8px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px}
|
|
295
378
|
.dsm-workbuddy-searched-more{margin-top:6px;padding:0;border:0;background:none;color:var(--dsw-alias-brand-primary,#5686fe);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-align:left}
|
|
296
379
|
.dsm-workbuddy-searched-more:hover{text-decoration:underline}
|
|
297
380
|
.dsm-workbuddy-usage-error{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
@@ -386,6 +469,12 @@ window.__ModuleLoader__.load({
|
|
|
386
469
|
* Order within each group is preserved from the Host: the store reports
|
|
387
470
|
* candidates in probe order, which is meaningful (the live file before its
|
|
388
471
|
* timestamped backups).
|
|
472
|
+
*
|
|
473
|
+
* There is deliberately no `encrypted` flag here. There used to be, for a
|
|
474
|
+
* notice rendered inside the list; that notice is gone because
|
|
475
|
+
* {@link signedOutNotice} now answers the encrypted cause in the paragraph, so
|
|
476
|
+
* the flag would have had no consumer — and a derived field kept "just in
|
|
477
|
+
* case" is how the list and the paragraph drift apart again.
|
|
389
478
|
*/
|
|
390
479
|
function searchedView(items) {
|
|
391
480
|
const interesting = items.filter((item) => INTERESTING_REASONS.includes(item.reason));
|
|
@@ -393,7 +482,6 @@ window.__ModuleLoader__.load({
|
|
|
393
482
|
interesting,
|
|
394
483
|
missing: items.filter((item) => !INTERESTING_REASONS.includes(item.reason)),
|
|
395
484
|
missingOpen: interesting.length === 0,
|
|
396
|
-
encrypted: items.some((item) => item.reason === "encrypted"),
|
|
397
485
|
total: items.length
|
|
398
486
|
};
|
|
399
487
|
}
|
|
@@ -440,10 +528,17 @@ window.__ModuleLoader__.load({
|
|
|
440
528
|
*
|
|
441
529
|
* `selectionLost` still wins outright: there the tokens are healthy and the
|
|
442
530
|
* advice is to re-pick an account, which no path list can replace.
|
|
531
|
+
*
|
|
532
|
+
* `encrypted` also outranks the generic hint, and for the same reason
|
|
533
|
+
* `wrong-region` does: the generic copy tells the user to sign in again, which
|
|
534
|
+
* is precisely the action that cannot work when the credential exists but is
|
|
535
|
+
* encrypted. Leaving it in the collapsed list meant the headline contradicted
|
|
536
|
+
* the detail below it — and the headline is the only part most users read.
|
|
443
537
|
*/
|
|
444
538
|
function signedOutNotice(input) {
|
|
445
539
|
if (input.selectionLost) return { key: "row.selectionLostMessage" };
|
|
446
540
|
if (input.searched.some((item) => item.reason === "wrong-region")) return { key: "row.signedOutWrongRegion" };
|
|
541
|
+
if (input.searched.some((item) => item.reason === "encrypted")) return { key: "row.signedOutEncrypted" };
|
|
447
542
|
if (input.searched.length > 0) return { key: "row.signedOutHint" };
|
|
448
543
|
return input.message === void 0 ? { key: "row.signedOutHint" } : {
|
|
449
544
|
key: "row.signedOutHint",
|
|
@@ -521,9 +616,12 @@ window.__ModuleLoader__.load({
|
|
|
521
616
|
* buried under a dozen "not found" lines. When nothing interesting was found
|
|
522
617
|
* the absent list IS the explanation, so it opens directly.
|
|
523
618
|
*
|
|
524
|
-
* An `encrypted` failure
|
|
525
|
-
*
|
|
526
|
-
*
|
|
619
|
+
* An `encrypted` failure deliberately adds NO notice of its own. It used to,
|
|
620
|
+
* because the paragraph above still said "sign in once in the desktop app" —
|
|
621
|
+
* the one action that cannot work when the credential exists but is encrypted.
|
|
622
|
+
* `signedOutNotice` now answers that cause in the paragraph itself, so a second
|
|
623
|
+
* copy here would restate it: the paragraph states the situation, this list
|
|
624
|
+
* supplies the detail, and the per-entry label already says what each path is.
|
|
527
625
|
*/
|
|
528
626
|
function SearchedPaths({ items, t }) {
|
|
529
627
|
const view = searchedView(items);
|
|
@@ -542,11 +640,6 @@ window.__ModuleLoader__.load({
|
|
|
542
640
|
className: "dsm-workbuddy-searched-hint",
|
|
543
641
|
children: t("row.searchedHint")
|
|
544
642
|
}),
|
|
545
|
-
view.encrypted ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
546
|
-
className: "dsm-workbuddy-searched-notice",
|
|
547
|
-
role: "status",
|
|
548
|
-
children: t("row.searchedEncryptedNotice")
|
|
549
|
-
}) : null,
|
|
550
643
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
551
644
|
className: "dsm-workbuddy-searched-list",
|
|
552
645
|
children: [view.interesting.map((item) => renderSearchedItem(item, t)), showMissing ? view.missing.map((item) => renderSearchedItem(item, t)) : null]
|
|
@@ -596,9 +689,9 @@ window.__ModuleLoader__.load({
|
|
|
596
689
|
return { background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)" };
|
|
597
690
|
}
|
|
598
691
|
/** Render WorkBuddy sign-in state, credits, and model selection as one card. */
|
|
599
|
-
function WorkBuddyCard({ t, settingsScope }) {
|
|
692
|
+
function WorkBuddyCard({ t, settingsScope, view }) {
|
|
600
693
|
if (t === void 0) throw new Error("WorkBuddy plugin card requires its translation function");
|
|
601
|
-
const [open, setOpen] = (0, react.useState)(
|
|
694
|
+
const [open, setOpen] = (0, react.useState)(view === "page");
|
|
602
695
|
/** The region whose tab is on screen; each tab is its own provider stack. */
|
|
603
696
|
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
604
697
|
/**
|
|
@@ -767,12 +860,19 @@ window.__ModuleLoader__.load({
|
|
|
767
860
|
* directory, model picks, image opt-ins and budgets survive a round trip.
|
|
768
861
|
* The Host withdraws or restores the provider route on the next `onChange`,
|
|
769
862
|
* which is what actually removes it from DSH's model picker.
|
|
863
|
+
*
|
|
864
|
+
* Goes through `writeRegionEnabled` rather than calling `scope.set()`
|
|
865
|
+
* directly: on the affected 0.1.7 deployments that scope settles without
|
|
866
|
+
* storing anything, so a direct write appeared to succeed and then silently
|
|
867
|
+
* reverted. The helper adds the landed-check and the Host-endpoint fallback
|
|
868
|
+
* that every other settings write already had.
|
|
770
869
|
*/
|
|
771
870
|
const toggleRegion = async (item, enabled) => {
|
|
772
|
-
if (settingsScope === void 0
|
|
871
|
+
if (settingsScope === void 0) return;
|
|
773
872
|
setTogglingRegion(item);
|
|
774
873
|
try {
|
|
775
|
-
|
|
874
|
+
const slot = nextRegionEnabled(settingsScope.getSnapshot().value, item, enabled)[item];
|
|
875
|
+
await writeRegionEnabled(settingsScope, item, enabled, typeof slot === "object" && slot !== null ? slot : {});
|
|
776
876
|
} finally {
|
|
777
877
|
if (mounted.current) setTogglingRegion(void 0);
|
|
778
878
|
}
|
|
@@ -845,13 +945,22 @@ window.__ModuleLoader__.load({
|
|
|
845
945
|
const activeEnabledIds = draft?.enabledIds ?? savedEnabledIds;
|
|
846
946
|
const savedImageIds = status.status === "signed-in" ? new Set(status.imageModelIds) : /* @__PURE__ */ new Set();
|
|
847
947
|
const activeImageIds = draft?.imageIds ?? savedImageIds;
|
|
848
|
-
|
|
849
|
-
const
|
|
850
|
-
const legacyContextBudgets = status.status === "signed-in" && status.region === "cn" ? configured?.contextBudgets : void 0;
|
|
851
|
-
const savedContextBudgetsSource = configuredRegion?.contextBudgets ?? legacyContextBudgets;
|
|
852
|
-
const savedContextBudgets = typeof savedContextBudgetsSource === "object" && savedContextBudgetsSource !== null ? savedContextBudgetsSource : {};
|
|
948
|
+
settingsScope?.getSnapshot().value;
|
|
949
|
+
const savedContextBudgets = status.status === "signed-in" ? status.contextBudgets ?? {} : {};
|
|
853
950
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
854
951
|
const dirty = draft !== void 0;
|
|
952
|
+
/**
|
|
953
|
+
* Whether the card's inputs accept edits.
|
|
954
|
+
*
|
|
955
|
+
* Deliberately NOT the settings scope's own `writable` flag: on the affected
|
|
956
|
+
* DSH 0.1.7 deployment that flag stays false (the scope initialises it false
|
|
957
|
+
* and only raises it once its mirror loads as a Host-backed form), which left
|
|
958
|
+
* every input `disabled` — clicking them did nothing at all. Writes no longer
|
|
959
|
+
* depend on that flag: `writeField` tries the scope and falls back to the
|
|
960
|
+
* plugin's own Host endpoint, which performs the mutate in the Host process.
|
|
961
|
+
* A bound scope is therefore all that is required to accept an edit.
|
|
962
|
+
*/
|
|
963
|
+
const canWrite = settingsScope !== void 0;
|
|
855
964
|
const editDraft = (edit) => {
|
|
856
965
|
setDrafts((prev) => ({
|
|
857
966
|
...prev,
|
|
@@ -982,8 +1091,7 @@ window.__ModuleLoader__.load({
|
|
|
982
1091
|
}),
|
|
983
1092
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
984
1093
|
"aria-hidden": "true",
|
|
985
|
-
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}
|
|
986
|
-
children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
|
|
1094
|
+
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`
|
|
987
1095
|
})
|
|
988
1096
|
]
|
|
989
1097
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1021,7 +1129,7 @@ window.__ModuleLoader__.load({
|
|
|
1021
1129
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1022
1130
|
type: "checkbox",
|
|
1023
1131
|
checked: regionOnState,
|
|
1024
|
-
disabled: togglingRegion === region ||
|
|
1132
|
+
disabled: togglingRegion === region || !canWrite,
|
|
1025
1133
|
"aria-label": t("row.tabSwitchAria", { region: region === "cn" ? t("row.tabCn") : t("row.tabGlobal") }),
|
|
1026
1134
|
onChange: (event) => {
|
|
1027
1135
|
toggleRegion(region, event.target.checked);
|
|
@@ -1085,7 +1193,7 @@ window.__ModuleLoader__.load({
|
|
|
1085
1193
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1086
1194
|
className: "dsm-workbuddy-usage-select",
|
|
1087
1195
|
value: status.accounts.find((account) => account.selected)?.id ?? "",
|
|
1088
|
-
disabled: switchingAccount ||
|
|
1196
|
+
disabled: switchingAccount || !canWrite,
|
|
1089
1197
|
onChange: (event) => {
|
|
1090
1198
|
switchAccount(event.currentTarget.value);
|
|
1091
1199
|
},
|
|
@@ -1194,7 +1302,7 @@ window.__ModuleLoader__.load({
|
|
|
1194
1302
|
status.recovery?.usableAccount === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1195
1303
|
type: "button",
|
|
1196
1304
|
className: "dsm-btn dsm-btn-outline",
|
|
1197
|
-
disabled: switchingAccount ||
|
|
1305
|
+
disabled: switchingAccount || !canWrite,
|
|
1198
1306
|
onClick: () => {
|
|
1199
1307
|
const target = status.recovery?.usableAccount;
|
|
1200
1308
|
if (target !== void 0) switchAccount(target.accountId);
|
|
@@ -1241,7 +1349,7 @@ window.__ModuleLoader__.load({
|
|
|
1241
1349
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1242
1350
|
type: "checkbox",
|
|
1243
1351
|
checked: activeEnabledIds.has(model.id),
|
|
1244
|
-
disabled:
|
|
1352
|
+
disabled: !canWrite || saving,
|
|
1245
1353
|
onChange: () => {
|
|
1246
1354
|
toggleModel(model.id);
|
|
1247
1355
|
}
|
|
@@ -1266,7 +1374,7 @@ window.__ModuleLoader__.load({
|
|
|
1266
1374
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1267
1375
|
type: "checkbox",
|
|
1268
1376
|
checked: activeImageIds.has(model.id),
|
|
1269
|
-
disabled:
|
|
1377
|
+
disabled: !canWrite || saving,
|
|
1270
1378
|
onChange: () => {
|
|
1271
1379
|
toggleImage(model.id);
|
|
1272
1380
|
}
|
|
@@ -1279,7 +1387,7 @@ window.__ModuleLoader__.load({
|
|
|
1279
1387
|
type: "radio",
|
|
1280
1388
|
name: `context-${model.id}`,
|
|
1281
1389
|
checked: (activeContextBudgets[model.id] ?? 2e5) === 2e5,
|
|
1282
|
-
disabled:
|
|
1390
|
+
disabled: !canWrite || saving,
|
|
1283
1391
|
onChange: () => {
|
|
1284
1392
|
setContextBudget(model.id, 2e5);
|
|
1285
1393
|
}
|
|
@@ -1287,7 +1395,7 @@ window.__ModuleLoader__.load({
|
|
|
1287
1395
|
type: "radio",
|
|
1288
1396
|
name: `context-${model.id}`,
|
|
1289
1397
|
checked: model.nativeContextWindow <= 2e5 || activeContextBudgets[model.id] === model.nativeContextWindow,
|
|
1290
|
-
disabled: model.nativeContextWindow <= 2e5 ||
|
|
1398
|
+
disabled: model.nativeContextWindow <= 2e5 || !canWrite || saving,
|
|
1291
1399
|
onChange: () => {
|
|
1292
1400
|
setContextBudget(model.id, model.nativeContextWindow);
|
|
1293
1401
|
}
|
|
@@ -1395,6 +1503,7 @@ window.__ModuleLoader__.load({
|
|
|
1395
1503
|
"row.signedOut": "Not signed in",
|
|
1396
1504
|
"row.signedOutHint": "Sign in once in the WorkBuddy desktop app; this plugin follows that sign-in automatically.",
|
|
1397
1505
|
"row.signedOutWrongRegion": "Your WorkBuddy sign-in was found, but it belongs to the other region tab. Switch tabs above to use it — there is nothing to sign in again.",
|
|
1506
|
+
"row.signedOutEncrypted": "Your WorkBuddy sign-in is present, but its token fields are encrypted and the plugin could not read the key. Install the WorkBuddy desktop app, or set WORKBUDDY_APP_EXECUTABLE to its executable — signing in again will not change this.",
|
|
1398
1507
|
"row.searchedTitle": "Paths checked",
|
|
1399
1508
|
"row.searchedHint": "If your WorkBuddy keeps its login somewhere else, point the plugin at it with the WORKBUDDY_AUTH_FILE environment variable.",
|
|
1400
1509
|
"row.reasonMissing": "not found",
|
|
@@ -1404,7 +1513,6 @@ window.__ModuleLoader__.load({
|
|
|
1404
1513
|
"row.reasonWrongRegion": "belongs to the other region",
|
|
1405
1514
|
"row.sourceDesktop": "desktop app",
|
|
1406
1515
|
"row.sourceDsh": "plugin copy",
|
|
1407
|
-
"row.searchedEncryptedNotice": "The credential file is there, but its token fields are encrypted and the key could not be read. Install the WorkBuddy desktop app, or set WORKBUDDY_APP_EXECUTABLE to its executable — signing in again on its own will not help.",
|
|
1408
1516
|
"row.searchedMore": "Show the {count} path(s) that were not found",
|
|
1409
1517
|
"row.signedIn": "Signed in: {accountName}",
|
|
1410
1518
|
"row.tokenExpiry": "Access token expires {expiresAt} (auto-renewal)",
|
|
@@ -1470,6 +1578,7 @@ window.__ModuleLoader__.load({
|
|
|
1470
1578
|
"row.signedOut": "未登录",
|
|
1471
1579
|
"row.signedOutHint": "在 WorkBuddy 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
1472
1580
|
"row.signedOutWrongRegion": "找到了你的 WorkBuddy 登录信息,但它属于另一个地区的标签页。切换到上方对应标签页即可使用——不需要重新登录。",
|
|
1581
|
+
"row.signedOutEncrypted": "你的 WorkBuddy 登录信息在,但其中的 token 字段是加密的,插件拿不到密钥。请安装 WorkBuddy 桌面 App,或用环境变量 WORKBUDDY_APP_EXECUTABLE 指定它的可执行文件——重新登录无法解决这个问题。",
|
|
1473
1582
|
"row.searchedTitle": "已检查的路径",
|
|
1474
1583
|
"row.searchedHint": "如果你的 WorkBuddy 把登录信息存在别处,可以用环境变量 WORKBUDDY_AUTH_FILE 指定。",
|
|
1475
1584
|
"row.reasonMissing": "不存在",
|
|
@@ -1479,7 +1588,6 @@ window.__ModuleLoader__.load({
|
|
|
1479
1588
|
"row.reasonWrongRegion": "属于另一个地区",
|
|
1480
1589
|
"row.sourceDesktop": "桌面 App",
|
|
1481
1590
|
"row.sourceDsh": "插件副本",
|
|
1482
|
-
"row.searchedEncryptedNotice": "凭据文件在,但其中的 token 字段是加密的,插件拿不到密钥。请安装 WorkBuddy 桌面 App,或用环境变量 WORKBUDDY_APP_EXECUTABLE 指定它的可执行文件——单纯重新登录解决不了这个问题。",
|
|
1483
1591
|
"row.searchedMore": "显示另外 {count} 条「不存在」的路径",
|
|
1484
1592
|
"row.signedIn": "已登录:{accountName}",
|
|
1485
1593
|
"row.tokenExpiry": "访问令牌 {expiresAt} 过期(自动续期)",
|
|
@@ -1535,12 +1643,17 @@ window.__ModuleLoader__.load({
|
|
|
1535
1643
|
//#region src/client/index.tsx
|
|
1536
1644
|
/** Stable browser-plugin name. */
|
|
1537
1645
|
const name = "dsh-connect-workbuddy-client";
|
|
1538
|
-
/**
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1646
|
+
/**
|
|
1647
|
+
* Client services required by the Plugin configuration contribution.
|
|
1648
|
+
*
|
|
1649
|
+
* Deliberately only the two services that exist on BOTH host lines. The
|
|
1650
|
+
* settings surface differs by line — 0.1.5 provides `settingsScope`, 0.1.7
|
|
1651
|
+
* replaces it with `configForms` — and Cordis' dependency gate is hard: any
|
|
1652
|
+
* inject entry the running line does not provide keeps `apply` from ever
|
|
1653
|
+
* running. Probing both via `ctx.get()` (which returns undefined, never
|
|
1654
|
+
* throws, for an absent service) is what lets one build serve both lines.
|
|
1655
|
+
*/
|
|
1656
|
+
const inject = ["slots", "locale"];
|
|
1544
1657
|
/** Register card copy and the WorkBuddy card under Plugin configuration. */
|
|
1545
1658
|
function apply(ctx) {
|
|
1546
1659
|
try {
|
|
@@ -1550,16 +1663,36 @@ window.__ModuleLoader__.load({
|
|
|
1550
1663
|
en
|
|
1551
1664
|
}), "dsh-connect-workbuddy: settings copy");
|
|
1552
1665
|
const t = ctx.locale.bind(namespace);
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1666
|
+
const softGet = (name) => ctx.get(name);
|
|
1667
|
+
let settingsScope;
|
|
1668
|
+
const forms = softGet("configForms");
|
|
1669
|
+
const legacy = softGet("settingsScope");
|
|
1670
|
+
if (forms !== void 0) {
|
|
1671
|
+
let ns = "workbuddy";
|
|
1672
|
+
try {
|
|
1673
|
+
const served = (forms.describe().getSnapshot().view?.namespaces ?? []).find((entry) => entry.ns === "workbuddy" || /workbuddy/i.test(entry.ns));
|
|
1674
|
+
if (served !== void 0) ns = served.ns;
|
|
1675
|
+
} catch {}
|
|
1676
|
+
settingsScope = forms.get(ns);
|
|
1677
|
+
} else if (legacy !== void 0) settingsScope = legacy.bind({ namespace: "workbuddy" });
|
|
1678
|
+
const registerCard = (slotName, key) => {
|
|
1679
|
+
try {
|
|
1680
|
+
ctx.slots.inject(slotName, () => ctx.slots.register({
|
|
1681
|
+
name: slotName,
|
|
1682
|
+
key,
|
|
1683
|
+
priority: 30,
|
|
1684
|
+
inject: () => settingsScope === void 0 ? { t } : {
|
|
1685
|
+
t,
|
|
1686
|
+
settingsScope
|
|
1687
|
+
}
|
|
1688
|
+
}, WorkBuddyCard));
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
console.error(`[dsh-connect-workbuddy] card slot "${slotName}" failed to register (host provider unaffected):`, error);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
registerCard("plugins.bundle.config", "dsh-connect-workbuddy");
|
|
1694
|
+
registerCard("plugins.row.config", "dsh-connect-workbuddy#dsh-connect-workbuddy");
|
|
1695
|
+
registerCard("settings.plugin.item", "workbuddy");
|
|
1563
1696
|
} catch (error) {
|
|
1564
1697
|
console.error("[dsh-connect-workbuddy] client card failed to load (host provider unaffected):", error);
|
|
1565
1698
|
}
|