dsh-workbuddy-connect 0.7.7 → 0.7.9
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 +18 -13
- package/README.md +18 -13
- package/THIRD_PARTY_NOTICES.txt +24 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +114 -33
- package/lib/{host-heartbeat-FytEpUGO.js → host-heartbeat-SJaZ33tR.js} +351 -19
- package/lib/index.d.ts +7 -2
- package/lib/index.js +398 -59
- package/package.json +27 -27
package/lib/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { $ as validAppVersion, A as extractDisplayErrorMessage, B as randomSentinel, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareCnChatBody, G as readCliVersion, H as FALLBACK_CN_APP_VERSION, I as prepareInternationalChatBody, J as WORKBUDDY_APP_VERSION_FILENAME, K as resolveChatIdentity, L as regionOf, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as resolveAppVersion, R as PROBE_EFFORT_CANDIDATES, S as CN_VARIANT, U as chatUserAgent, V as CN_APP_VERSION_FILENAME, W as fallbackChatIdentity, X as installedAppVersion, Y as appUserAgent, Z as readBundleVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as validCliVersion, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as probeModel } from "./host-heartbeat-
|
|
1
|
+
import { $ as validAppVersion, A as extractDisplayErrorMessage, B as randomSentinel, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareCnChatBody, G as readCliVersion, H as FALLBACK_CN_APP_VERSION, I as prepareInternationalChatBody, J as WORKBUDDY_APP_VERSION_FILENAME, K as resolveChatIdentity, L as regionOf, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as resolveAppVersion, R as PROBE_EFFORT_CANDIDATES, S as CN_VARIANT, U as chatUserAgent, V as CN_APP_VERSION_FILENAME, W as fallbackChatIdentity, X as installedAppVersion, Y as appUserAgent, Z as readBundleVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as validCliVersion, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as probeModel } from "./host-heartbeat-SJaZ33tR.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
6
5
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
9
9
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
@@ -577,13 +577,6 @@ var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
|
|
|
577
577
|
};
|
|
578
578
|
//#endregion
|
|
579
579
|
//#region src/loopback.ts
|
|
580
|
-
/**
|
|
581
|
-
* Shared loopback gates for the plugin's local HTTP surfaces: the loopback
|
|
582
|
-
* shim and the same-origin web-status route. Both are only ever meant to be
|
|
583
|
-
* addressed through the machine's loopback interface.
|
|
584
|
-
*
|
|
585
|
-
* @module dsh-workbuddy-connect/loopback
|
|
586
|
-
*/
|
|
587
580
|
/** Loopback hostnames a local plugin surface may be addressed by. */
|
|
588
581
|
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
589
582
|
"127.0.0.1",
|
|
@@ -623,6 +616,163 @@ function originIsLoopback(origin) {
|
|
|
623
616
|
return false;
|
|
624
617
|
}
|
|
625
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Ports fetch refuses to connect to, per the fetch standard's port blocking
|
|
621
|
+
* table (https://fetch.spec.whatwg.org/#port-blocking). Kept in sync with the
|
|
622
|
+
* `badPorts` table undici ships: only the entries a host's ephemeral range can
|
|
623
|
+
* actually reach matter, but carrying the whole list costs nothing and keeps
|
|
624
|
+
* the check honest on machines with a customised range.
|
|
625
|
+
*/
|
|
626
|
+
const FETCH_BLOCKED_PORTS = /* @__PURE__ */ new Set([
|
|
627
|
+
1,
|
|
628
|
+
7,
|
|
629
|
+
9,
|
|
630
|
+
11,
|
|
631
|
+
13,
|
|
632
|
+
15,
|
|
633
|
+
17,
|
|
634
|
+
19,
|
|
635
|
+
20,
|
|
636
|
+
21,
|
|
637
|
+
22,
|
|
638
|
+
23,
|
|
639
|
+
25,
|
|
640
|
+
37,
|
|
641
|
+
42,
|
|
642
|
+
43,
|
|
643
|
+
53,
|
|
644
|
+
69,
|
|
645
|
+
77,
|
|
646
|
+
79,
|
|
647
|
+
87,
|
|
648
|
+
95,
|
|
649
|
+
101,
|
|
650
|
+
102,
|
|
651
|
+
103,
|
|
652
|
+
104,
|
|
653
|
+
109,
|
|
654
|
+
110,
|
|
655
|
+
111,
|
|
656
|
+
113,
|
|
657
|
+
115,
|
|
658
|
+
117,
|
|
659
|
+
119,
|
|
660
|
+
123,
|
|
661
|
+
135,
|
|
662
|
+
137,
|
|
663
|
+
139,
|
|
664
|
+
143,
|
|
665
|
+
161,
|
|
666
|
+
179,
|
|
667
|
+
389,
|
|
668
|
+
427,
|
|
669
|
+
465,
|
|
670
|
+
512,
|
|
671
|
+
513,
|
|
672
|
+
514,
|
|
673
|
+
515,
|
|
674
|
+
526,
|
|
675
|
+
530,
|
|
676
|
+
531,
|
|
677
|
+
532,
|
|
678
|
+
540,
|
|
679
|
+
548,
|
|
680
|
+
554,
|
|
681
|
+
556,
|
|
682
|
+
563,
|
|
683
|
+
587,
|
|
684
|
+
601,
|
|
685
|
+
636,
|
|
686
|
+
989,
|
|
687
|
+
990,
|
|
688
|
+
993,
|
|
689
|
+
995,
|
|
690
|
+
1719,
|
|
691
|
+
1720,
|
|
692
|
+
1723,
|
|
693
|
+
2049,
|
|
694
|
+
3659,
|
|
695
|
+
4045,
|
|
696
|
+
4190,
|
|
697
|
+
5060,
|
|
698
|
+
5061,
|
|
699
|
+
6e3,
|
|
700
|
+
6566,
|
|
701
|
+
6665,
|
|
702
|
+
6666,
|
|
703
|
+
6667,
|
|
704
|
+
6668,
|
|
705
|
+
6669,
|
|
706
|
+
6679,
|
|
707
|
+
6697,
|
|
708
|
+
10080
|
|
709
|
+
]);
|
|
710
|
+
/** Whether fetch (and therefore pi-ai) refuses to connect to `port`. */
|
|
711
|
+
function isFetchBlockedPort(port) {
|
|
712
|
+
return FETCH_BLOCKED_PORTS.has(port);
|
|
713
|
+
}
|
|
714
|
+
/** Bind attempts allowed before giving up, including the first. */
|
|
715
|
+
const DEFAULT_MAX_ATTEMPTS = 16;
|
|
716
|
+
/**
|
|
717
|
+
* Bind `server` to an ephemeral port fetch will actually connect to, and
|
|
718
|
+
* resolve with the address it ended up on.
|
|
719
|
+
*
|
|
720
|
+
* `listen(0)` lets the OS hand out any free ephemeral port, and a handful of
|
|
721
|
+
* the ports it can hand out are on the fetch standard's bad-port list: fetch
|
|
722
|
+
* refuses to connect to them and fails with `TypeError: fetch failed` before a
|
|
723
|
+
* socket is opened. The shim is only ever reached through fetch (pi-ai calls
|
|
724
|
+
* it), so settling on one of those ports leaves the plugin with an endpoint
|
|
725
|
+
* that answers raw HTTP and rejects every real request. The port is not
|
|
726
|
+
* negotiable at request time, so the only fix is to bind again.
|
|
727
|
+
*
|
|
728
|
+
* A blocked port is closed and rebound; a listen error is fatal, as it was
|
|
729
|
+
* before, because nothing about picking another port would fix it.
|
|
730
|
+
*/
|
|
731
|
+
async function bindToUsablePort(server, options) {
|
|
732
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
|
|
733
|
+
const isPortBlocked = options.isPortBlocked ?? isFetchBlockedPort;
|
|
734
|
+
let lastBlockedPort = 0;
|
|
735
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
736
|
+
const address = await listenOnEphemeralPort(server, options.host);
|
|
737
|
+
if (!isPortBlocked(address.port)) return address;
|
|
738
|
+
lastBlockedPort = address.port;
|
|
739
|
+
options.onBlockedPort?.(address.port, attempt);
|
|
740
|
+
await closeQuietly(server);
|
|
741
|
+
}
|
|
742
|
+
throw new Error(`could not bind a fetch-reachable loopback port after ${maxAttempts} attempts (last blocked port: ${lastBlockedPort})`);
|
|
743
|
+
}
|
|
744
|
+
/** One `listen(0)` on `host`, resolving with the address the OS handed out. */
|
|
745
|
+
function listenOnEphemeralPort(server, host) {
|
|
746
|
+
return new Promise((resolve, reject) => {
|
|
747
|
+
const cleanup = () => {
|
|
748
|
+
server.off("listening", onListening);
|
|
749
|
+
server.off("error", onError);
|
|
750
|
+
};
|
|
751
|
+
const onListening = () => {
|
|
752
|
+
cleanup();
|
|
753
|
+
const address = server.address();
|
|
754
|
+
if (address === null || typeof address === "string") {
|
|
755
|
+
reject(/* @__PURE__ */ new Error("workbuddy shim has no listening address"));
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
resolve(address);
|
|
759
|
+
};
|
|
760
|
+
const onError = (error) => {
|
|
761
|
+
cleanup();
|
|
762
|
+
reject(error);
|
|
763
|
+
};
|
|
764
|
+
server.once("listening", onListening);
|
|
765
|
+
server.once("error", onError);
|
|
766
|
+
server.listen(0, host);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/** Close between attempts; a listener that already closed is not an error here. */
|
|
770
|
+
function closeQuietly(server) {
|
|
771
|
+
return new Promise((resolve) => {
|
|
772
|
+
server.closeAllConnections();
|
|
773
|
+
server.close(() => resolve());
|
|
774
|
+
});
|
|
775
|
+
}
|
|
626
776
|
//#endregion
|
|
627
777
|
//#region src/shim.ts
|
|
628
778
|
/**
|
|
@@ -713,11 +863,12 @@ function createWorkBuddyShim(options) {
|
|
|
713
863
|
const server = createServer((req, res) => {
|
|
714
864
|
handle(req, res);
|
|
715
865
|
});
|
|
716
|
-
const ready =
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
866
|
+
const ready = bindToUsablePort(server, {
|
|
867
|
+
host: "127.0.0.1",
|
|
868
|
+
onBlockedPort: (port, attempt) => {
|
|
869
|
+
logger?.warn(`dsh-workbuddy-connect: port ${port} is blocked for fetch; rebinding (attempt ${attempt})`);
|
|
870
|
+
}
|
|
871
|
+
}).then(() => void 0);
|
|
721
872
|
const baseUrl = () => {
|
|
722
873
|
const address = server.address();
|
|
723
874
|
if (address === null || typeof address === "string") throw new Error("workbuddy shim has no listening address");
|
|
@@ -1282,6 +1433,8 @@ async function workBuddyWebStatus(deps) {
|
|
|
1282
1433
|
...model.billing?.badges !== void 0 && model.billing.badges.length > 0 ? { badges: model.billing.badges } : {},
|
|
1283
1434
|
...rate === void 0 ? {} : { credits: rate },
|
|
1284
1435
|
...model.billing?.rateUnknown === true ? { rateUnknown: true } : {},
|
|
1436
|
+
...(model.reasoning?.supportedEfforts?.length ?? 0) > 0 ? { declaredEfforts: model.reasoning.supportedEfforts } : {},
|
|
1437
|
+
...model.reasoning?.defaultEffort !== void 0 ? { defaultEffort: model.reasoning.defaultEffort } : {},
|
|
1285
1438
|
...typeof model.contextWindow === "number" && model.contextWindow > 0 ? { contextWindow: model.contextWindow } : {},
|
|
1286
1439
|
...typeof defaultContextWindow === "number" && defaultContextWindow > 0 && defaultContextWindow < model.contextWindow ? { defaultContextWindow } : {},
|
|
1287
1440
|
...maxContextWindow === void 0 || maxContextWindow <= defaultContextWindow ? {} : { maxContextWindow },
|
|
@@ -1860,6 +2013,38 @@ function legacySettingsOf(settings) {
|
|
|
1860
2013
|
if (typeof candidate.update !== "function") return void 0;
|
|
1861
2014
|
return settings;
|
|
1862
2015
|
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Narrow one host settings service to the modern DSH 0.2.x SettingsForms API, or
|
|
2018
|
+
* `undefined` when this host's generation does not carry it.
|
|
2019
|
+
*/
|
|
2020
|
+
function modernSettingsOf(settings) {
|
|
2021
|
+
if (typeof settings !== "object" || settings === null) return void 0;
|
|
2022
|
+
const candidate = settings;
|
|
2023
|
+
if (typeof candidate.installSection === "function") return void 0;
|
|
2024
|
+
if (typeof candidate.describe !== "function") return void 0;
|
|
2025
|
+
if (typeof candidate.update !== "function") return void 0;
|
|
2026
|
+
return settings;
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Resolve this plugin's own profile entry id from SettingsForms descriptors.
|
|
2030
|
+
*
|
|
2031
|
+
* The real `describe()` (dsh-settings 0.2.1-rc.2, `SettingsForms.describe`)
|
|
2032
|
+
* hands out *serialized* form schemas (`volatileForm(schema).toJSON()`) and
|
|
2033
|
+
* no fiber reference, so neither a `desc.schema === Config` identity check
|
|
2034
|
+
* nor a fiber comparison can ever succeed — identity has to come from the
|
|
2035
|
+
* entry id the loader attached to this plugin's own fiber
|
|
2036
|
+
* (`ctx.fiber.entry.options.id`; the tree-qualified `entry.id` includes
|
|
2037
|
+
* parent prefixes that SettingsForms does not use). The package-name literal is the only fallback, for hosts where the
|
|
2038
|
+
* plugin was loaded programmatically and carries no entry: entry ids are
|
|
2039
|
+
* unique per composition, so the literal can only name this plugin. Neither
|
|
2040
|
+
* route matching an entry that is served answers `undefined` — never a
|
|
2041
|
+
* guess, because a wrong guess silently writes another plugin's
|
|
2042
|
+
* configuration.
|
|
2043
|
+
*/
|
|
2044
|
+
function resolveModernSettingsEntryId(descriptors, ownEntryId) {
|
|
2045
|
+
if (ownEntryId !== void 0) return descriptors.find((row) => row.ns === ownEntryId)?.ns;
|
|
2046
|
+
return descriptors.find((row) => row.ns === "dsh-workbuddy-connect")?.ns;
|
|
2047
|
+
}
|
|
1863
2048
|
//#endregion
|
|
1864
2049
|
//#region src/index.ts
|
|
1865
2050
|
/** Stable Cordis plugin name. */
|
|
@@ -1938,7 +2123,7 @@ const AUTH_FILE_FIELD = z.string().description("WorkBuddy desktop auth file (def
|
|
|
1938
2123
|
const AUTH_FILE_AI_FIELD = z.string().description("WorkBuddy AI desktop auth file (defaults to the app's own location)");
|
|
1939
2124
|
/** Probe authorization (shared by the plugin schema and the CN section). */
|
|
1940
2125
|
const PROBE_CONSENT_FIELD = z.boolean().default(false).description("Authorize reasoning-effort probes (each probe sends real requests that may consume credit)");
|
|
1941
|
-
const MAXIMUM_CONTEXT_WINDOW_FIELD = z.boolean().default(true).description("Use the largest context window declared by WorkBuddy AI when alternatives are available (on by default)");
|
|
2126
|
+
const MAXIMUM_CONTEXT_WINDOW_FIELD = z.boolean().default(true).volatile().description("Use the largest context window declared by WorkBuddy AI when alternatives are available (on by default)");
|
|
1942
2127
|
const Config = z.object({
|
|
1943
2128
|
authFile: AUTH_FILE_FIELD,
|
|
1944
2129
|
authFileAI: AUTH_FILE_AI_FIELD,
|
|
@@ -1946,6 +2131,17 @@ const Config = z.object({
|
|
|
1946
2131
|
useMaximumContextWindow: MAXIMUM_CONTEXT_WINDOW_FIELD
|
|
1947
2132
|
});
|
|
1948
2133
|
/**
|
|
2134
|
+
* Read the context-window preference off a resolved Config. Fields marked
|
|
2135
|
+
* `.volatile()` materialize as cosmokit volatile containers (`{ get() }`) so
|
|
2136
|
+
* the host can live-update them without remounting the plugin; plain hosts
|
|
2137
|
+
* and values set through this plugin are plain booleans. Both spellings
|
|
2138
|
+
* answer here.
|
|
2139
|
+
*/
|
|
2140
|
+
function plainConfigBoolean(value) {
|
|
2141
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") return plainConfigBoolean(value.get());
|
|
2142
|
+
return value === void 0 ? void 0 : value === true;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
1949
2145
|
* The CN side's settings section: only the fields that side edits.
|
|
1950
2146
|
*
|
|
1951
2147
|
* The section's namespace is what serves these fields to `settings.yaml` and
|
|
@@ -2007,7 +2203,7 @@ function createVariantRuntime(config, variant, current, identityOf, accountOf, k
|
|
|
2007
2203
|
});
|
|
2008
2204
|
const fallback = fallbackFor(variant);
|
|
2009
2205
|
const catalog = new WorkBuddyCatalog(fallback);
|
|
2010
|
-
if (variant.id !== CN_VARIANT.id) catalog.setUseMaximumContextWindow(config.useMaximumContextWindow === true);
|
|
2206
|
+
if (variant.id !== CN_VARIANT.id) catalog.setUseMaximumContextWindow(plainConfigBoolean(config.useMaximumContextWindow) === true);
|
|
2011
2207
|
catalog.setVisible(false);
|
|
2012
2208
|
const probeStore = new WorkBuddyProbeStore({
|
|
2013
2209
|
pluginVersion: WORKBUDDY_CONNECT_VERSION,
|
|
@@ -2194,6 +2390,8 @@ function apply(ctx, config) {
|
|
|
2194
2390
|
* checkbox that could not be saved.
|
|
2195
2391
|
*/
|
|
2196
2392
|
let legacySettingsAvailable = false;
|
|
2393
|
+
/** Whether modern DSH 0.2.x SettingsForms service is available to persist settings. */
|
|
2394
|
+
let settingsAvailable = false;
|
|
2197
2395
|
/**
|
|
2198
2396
|
* Point a variant at an account identity, invalidating whatever the previous
|
|
2199
2397
|
* one left behind.
|
|
@@ -2273,7 +2471,7 @@ function apply(ctx, config) {
|
|
|
2273
2471
|
disabled: runtime.visibilityStore.disabled(account)
|
|
2274
2472
|
};
|
|
2275
2473
|
},
|
|
2276
|
-
...runtime.variant.id === CN_VARIANT.id ? {} : { useMaximumContextWindow: () => legacySettingsAvailable ? current().useMaximumContextWindow === true : void 0 }
|
|
2474
|
+
...runtime.variant.id === CN_VARIANT.id ? {} : { useMaximumContextWindow: () => legacySettingsAvailable || settingsAvailable ? plainConfigBoolean(current().useMaximumContextWindow) === true : void 0 }
|
|
2277
2475
|
});
|
|
2278
2476
|
registerWorkBuddyProbeRoute(webCtx, {
|
|
2279
2477
|
path: runtime.variant.probePath,
|
|
@@ -2348,50 +2546,191 @@ function apply(ctx, config) {
|
|
|
2348
2546
|
});
|
|
2349
2547
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
2350
2548
|
const legacy = legacySettingsOf(settingsCtx.settings);
|
|
2351
|
-
if (legacy
|
|
2352
|
-
|
|
2549
|
+
if (legacy !== void 0) {
|
|
2550
|
+
legacySettingsAvailable = true;
|
|
2551
|
+
/** Section sources; each falls back to its own slice when its side unloads. */
|
|
2552
|
+
const sources = {
|
|
2553
|
+
cn: () => config,
|
|
2554
|
+
ai: () => config
|
|
2555
|
+
};
|
|
2556
|
+
/** Merge both sections into the whole config the rest of the plugin reads. */
|
|
2557
|
+
const merged = () => {
|
|
2558
|
+
const aiMaximum = plainConfigBoolean(sources.ai().useMaximumContextWindow);
|
|
2559
|
+
return {
|
|
2560
|
+
...sources.cn().authFile === void 0 ? {} : { authFile: sources.cn().authFile },
|
|
2561
|
+
...sources.cn().probeConsent === void 0 ? {} : { probeConsent: sources.cn().probeConsent },
|
|
2562
|
+
...sources.ai().authFileAI === void 0 ? {} : { authFileAI: sources.ai().authFileAI },
|
|
2563
|
+
...aiMaximum === void 0 ? {} : { useMaximumContextWindow: aiMaximum }
|
|
2564
|
+
};
|
|
2565
|
+
};
|
|
2566
|
+
const applyMaximumContextWindow = (next) => {
|
|
2567
|
+
const runtime = runtimes.find((candidate) => candidate.variant.id !== CN_VARIANT.id);
|
|
2568
|
+
if (runtime?.catalog.setUseMaximumContextWindow(next.useMaximumContextWindow === true)) runtime.invalidate();
|
|
2569
|
+
};
|
|
2570
|
+
const repointStores = () => {
|
|
2571
|
+
const next = merged();
|
|
2572
|
+
applyMaximumContextWindow(next);
|
|
2573
|
+
for (const runtime of runtimes) runtime.store.setDesktopPath(configuredAuthFile(next, runtime.variant));
|
|
2574
|
+
};
|
|
2575
|
+
legacy.installSection(ctx, WORKBUDDY_SETTINGS_NS, CN_SECTION, config, {
|
|
2576
|
+
setSource(source) {
|
|
2577
|
+
sources.cn = source;
|
|
2578
|
+
current = merged;
|
|
2579
|
+
},
|
|
2580
|
+
onChange: repointStores
|
|
2581
|
+
});
|
|
2582
|
+
legacy.installSection(ctx, WORKBUDDY_AI_SETTINGS_NS, AI_SECTION, config, {
|
|
2583
|
+
setSource(source) {
|
|
2584
|
+
sources.ai = source;
|
|
2585
|
+
current = merged;
|
|
2586
|
+
},
|
|
2587
|
+
onChange: repointStores
|
|
2588
|
+
});
|
|
2589
|
+
setMaximumContextWindow = async (enabled) => {
|
|
2590
|
+
await legacy.update(WORKBUDDY_AI_SETTINGS_NS, { useMaximumContextWindow: enabled });
|
|
2591
|
+
return { state: "updated" };
|
|
2592
|
+
};
|
|
2353
2593
|
return;
|
|
2354
2594
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2595
|
+
const modern = modernSettingsOf(settingsCtx.settings);
|
|
2596
|
+
if (modern !== void 0) {
|
|
2597
|
+
/**
|
|
2598
|
+
* The live preference as the host's own Config reports it.
|
|
2599
|
+
*
|
|
2600
|
+
* The forms layer is the single source of truth on a 0.2.x host: a write
|
|
2601
|
+
* lands in the profile document and comes back through `describe()`, so
|
|
2602
|
+
* nothing has to be mirrored in memory here (and a mirror is exactly how
|
|
2603
|
+
* runtime and saved state drift apart). The persisted default already
|
|
2604
|
+
* applies — the preference field defaults to `true` and the composition
|
|
2605
|
+
* handed this plugin its resolved `config`.
|
|
2606
|
+
*/
|
|
2607
|
+
let liveConfig = {
|
|
2608
|
+
...config,
|
|
2609
|
+
useMaximumContextWindow: plainConfigBoolean(config.useMaximumContextWindow) ?? true
|
|
2610
|
+
};
|
|
2611
|
+
/** Whether {@link liveConfig} came from a settings descriptor (see below). */
|
|
2612
|
+
let liveConfigBound = false;
|
|
2613
|
+
/**
|
|
2614
|
+
* Read this plugin's own entry out of the host's forms.
|
|
2615
|
+
*
|
|
2616
|
+
* The namespace on a 0.2.x host is the *profile entry id* — not the
|
|
2617
|
+
* `SettingsNamespace` the 0.1.x sections were installed under — so it is
|
|
2618
|
+
* whatever the user named this plugin in the profile composition.
|
|
2619
|
+
*
|
|
2620
|
+
* Identity cannot come from the descriptors themselves: `describe()`
|
|
2621
|
+
* hands out *serialized* forms (`volatileForm(schema).toJSON()`), never
|
|
2622
|
+
* the live Config object, and its rows carry no fiber — so
|
|
2623
|
+
* `desc.schema === Config` and fiber comparisons are always false
|
|
2624
|
+
* against the real service. The loader instead keys entries by id and
|
|
2625
|
+
* attaches them to the fibers it runs (`ctx.fiber.entry.options.id`, unlike the tree-qualified `entry.id`), which is
|
|
2626
|
+
* what {@link resolveModernSettingsEntryId} reads; the package-name
|
|
2627
|
+
* literal stays as the only fallback (entry ids are unique per
|
|
2628
|
+
* composition, so it can only name this plugin). Anything unmatched
|
|
2629
|
+
* answers `undefined` — a wrong guess would silently write another
|
|
2630
|
+
* plugin's configuration.
|
|
2631
|
+
*/
|
|
2632
|
+
const settingsEntryId = () => {
|
|
2633
|
+
try {
|
|
2634
|
+
const entry = ctx.fiber?.entry;
|
|
2635
|
+
return resolveModernSettingsEntryId(modern.describe(), entry?.options?.id ?? entry?.id);
|
|
2636
|
+
} catch (error) {
|
|
2637
|
+
ctx.logger.warn("dsh-workbuddy-connect: reading this plugin's settings entry failed: %s", error);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
};
|
|
2641
|
+
/**
|
|
2642
|
+
* Apply whatever the settings document now says.
|
|
2643
|
+
*
|
|
2644
|
+
* The value read is the descriptor's `value` — the projection of the live
|
|
2645
|
+
* fiber config, i.e. what the loader actually resolved — rather than
|
|
2646
|
+
* `user`, which names only the profile's own section and would miss an
|
|
2647
|
+
* inherited or default value that is nonetheless in effect.
|
|
2648
|
+
*/
|
|
2649
|
+
const applyModernConfig = () => {
|
|
2650
|
+
if (!liveConfigBound) return;
|
|
2651
|
+
const runtime = runtimes.find((candidate) => candidate.variant.id !== CN_VARIANT.id);
|
|
2652
|
+
if (runtime?.catalog.setUseMaximumContextWindow(plainConfigBoolean(liveConfig.useMaximumContextWindow) === true)) runtime.invalidate();
|
|
2653
|
+
};
|
|
2654
|
+
setMaximumContextWindow = async (enabled) => {
|
|
2655
|
+
const ns = settingsEntryId();
|
|
2656
|
+
if (ns === void 0) return {
|
|
2657
|
+
state: "failed",
|
|
2658
|
+
reason: "no configurable settings entry for this plugin"
|
|
2659
|
+
};
|
|
2660
|
+
try {
|
|
2661
|
+
await modern.update(ns, { useMaximumContextWindow: enabled });
|
|
2662
|
+
if ((modern.describe().find((entry) => entry.ns === ns)?.value)?.useMaximumContextWindow !== enabled) return {
|
|
2663
|
+
state: "failed",
|
|
2664
|
+
reason: "the settings document did not accept the context-window preference"
|
|
2665
|
+
};
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
return {
|
|
2668
|
+
state: "failed",
|
|
2669
|
+
reason: error instanceof Error ? error.message.slice(0, 300) : String(error)
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
liveConfig = {
|
|
2673
|
+
...liveConfig,
|
|
2674
|
+
useMaximumContextWindow: enabled
|
|
2675
|
+
};
|
|
2676
|
+
current = () => liveConfig;
|
|
2677
|
+
applyModernConfig();
|
|
2678
|
+
return { state: "updated" };
|
|
2679
|
+
};
|
|
2680
|
+
let retry;
|
|
2681
|
+
let retryIndex = 0;
|
|
2682
|
+
let bindingStopped = false;
|
|
2683
|
+
const retryDelays = [
|
|
2684
|
+
250,
|
|
2685
|
+
500,
|
|
2686
|
+
1e3,
|
|
2687
|
+
2e3,
|
|
2688
|
+
4e3,
|
|
2689
|
+
8e3
|
|
2690
|
+
];
|
|
2691
|
+
const clearRetry = () => {
|
|
2692
|
+
clearTimeout(retry);
|
|
2693
|
+
retry = void 0;
|
|
2694
|
+
};
|
|
2695
|
+
const bindModernConfig = () => {
|
|
2696
|
+
if (bindingStopped) return;
|
|
2697
|
+
try {
|
|
2698
|
+
const ns = settingsEntryId();
|
|
2699
|
+
const entry = ns === void 0 ? void 0 : modern.describe().find((desc) => desc.ns === ns);
|
|
2700
|
+
settingsAvailable = (entry?.schema === void 0 ? void 0 : new z(entry.schema))?.dict?.useMaximumContextWindow?.type === "boolean";
|
|
2701
|
+
if (settingsAvailable) {
|
|
2702
|
+
const stored = (entry?.value)?.useMaximumContextWindow;
|
|
2703
|
+
liveConfig = {
|
|
2704
|
+
...liveConfig,
|
|
2705
|
+
useMaximumContextWindow: plainConfigBoolean(stored) ?? plainConfigBoolean(liveConfig.useMaximumContextWindow) ?? true
|
|
2706
|
+
};
|
|
2707
|
+
liveConfigBound = true;
|
|
2708
|
+
current = () => liveConfig;
|
|
2709
|
+
applyModernConfig();
|
|
2710
|
+
clearRetry();
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
} catch (error) {
|
|
2714
|
+
settingsAvailable = false;
|
|
2715
|
+
ctx.logger.warn("dsh-workbuddy-connect: reading the stored context-window preference failed: %s", error);
|
|
2716
|
+
}
|
|
2717
|
+
if (retry === void 0 && retryIndex < retryDelays.length) retry = setTimeout(() => {
|
|
2718
|
+
retry = void 0;
|
|
2719
|
+
bindModernConfig();
|
|
2720
|
+
}, retryDelays[retryIndex++]);
|
|
2721
|
+
};
|
|
2722
|
+
settingsCtx.effect(() => () => {
|
|
2723
|
+
bindingStopped = true;
|
|
2724
|
+
clearRetry();
|
|
2725
|
+
settingsAvailable = false;
|
|
2726
|
+
});
|
|
2727
|
+
settingsCtx.effect(() => ctx.on("settings/document-updated", () => {
|
|
2728
|
+
bindModernConfig();
|
|
2729
|
+
}));
|
|
2730
|
+
bindModernConfig();
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
ctx.logger.warn("dsh-workbuddy-connect: host settings service has no installSection or SettingsForms API; per-variant settings and the maximum-context preference are unavailable");
|
|
2395
2734
|
});
|
|
2396
2735
|
ctx.effect(() => () => {
|
|
2397
2736
|
stopped = true;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-workbuddy-connect",
|
|
3
3
|
"displayName": "DSH WorkBuddy Connect",
|
|
4
4
|
"description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.9",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"cordis.patch.yml",
|
|
42
42
|
"README.md",
|
|
43
43
|
"README.en.md",
|
|
44
|
-
"LICENSE"
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"THIRD_PARTY_NOTICES.txt"
|
|
45
46
|
],
|
|
46
47
|
"license": "MIT",
|
|
47
48
|
"repository": {
|
|
@@ -71,36 +72,35 @@
|
|
|
71
72
|
},
|
|
72
73
|
"peerDependencies": {
|
|
73
74
|
"@deepseek-ai/cordis": "^4.0.5",
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-settings": "0.2.1-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
|
|
79
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.2",
|
|
81
81
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
82
82
|
"react": "^18.2.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@deepseek-ai/cordis": "^4.0.5",
|
|
86
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.
|
|
87
|
-
"@deepseek-ai/dsh-attachment": "0.2.1-rc.
|
|
88
|
-
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.
|
|
89
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.
|
|
90
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.1-rc.
|
|
91
|
-
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.
|
|
92
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.1-rc.
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.
|
|
95
|
-
"@deepseek-ai/dsh-credentials": "0.2.1-rc.
|
|
96
|
-
"@deepseek-ai/dsh-authorization": "0.2.1-rc.
|
|
97
|
-
"@deepseek-ai/dsh-fs": "0.2.1-rc.
|
|
98
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.1-rc.
|
|
99
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.
|
|
100
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.
|
|
101
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
102
|
-
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.
|
|
103
|
-
"@deepseek-ai/dsh-settings": "0.2.1-rc.
|
|
86
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-credentials": "0.2.1-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-authorization": "0.2.1-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-fs": "0.2.1-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.1-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
|
|
101
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
|
|
102
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.2",
|
|
104
104
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
105
105
|
"@earendil-works/pi-ai": "1.3.0",
|
|
106
106
|
"@types/node": "^22.20.0",
|