@chatcode/chatcode-cli 3.0.3 → 3.0.4-rc.1
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.i18n.yaml +10 -10
- package/README.md +16 -1
- package/README.zh.md +16 -1
- package/lib/bin.js +62 -11
- package/lib/{command-errors-CBJRI2gy.js → command-errors-BkCHTQHj.js} +12 -12
- package/lib/{command-trees-Dvq4C7a4.js → command-trees-5HFyC5sI.js} +2 -2
- package/lib/{component-identity-DPdJglJO.js → component-identity-De91VHwW.js} +3 -3
- package/lib/{dist-w_sIm4VD.js → dist-CsDWUvQ4.js} +231 -231
- package/lib/{dump-config-CauO75nE.js → dump-config-BcH-VYuv.js} +5 -5
- package/lib/dump-config-CyopmD85.js +2 -0
- package/lib/{dump-config-schema-CObLWWaW.js → dump-config-schema-DM0fb6Ns.js} +2 -2
- package/lib/{extension-events-Be9LWvHD.js → extension-events-CdYCRP0o.js} +26 -26
- package/lib/{frames-7PAg0Q9b.js → frames-DOX-Jgk8.js} +4 -4
- package/lib/headless-compat.js +2 -2
- package/lib/{host-access-DeCjoRQP.js → host-access-Bk4xps5E.js} +1 -1
- package/lib/{keymap-CRUxM_qn.js → keymap-CJ1kvLR2.js} +17 -16
- package/lib/{plugin-CDFSDE_y.js → plugin-XR7ncbnW.js} +2928 -2240
- package/lib/{plugin-Cn-hyYY8.js → plugin-eJjD6r12.js} +1 -1
- package/lib/{profile-boot-SJGbpLgN.js → profile-boot-BNQry1NM.js} +97 -14
- package/lib/profile-boot.js +2 -2
- package/lib/{scenes-D6jV1EEi.js → scenes-CAmWncZD.js} +3 -3
- package/lib/{settings-sections-DJQRNBo9.js → settings-sections-CakcvBv9.js} +7 -3
- package/lib/statusBarSettings-BPPS_Usm.js +382 -0
- package/lib/{supports-color-CcW-FGjr.js → supports-color-DyChQwlI.js} +2 -2
- package/lib/{themes-BCly4_y9.js → themes-OjcUpZzw.js} +13 -13
- package/lib/tui-compat-command-trees.js +1 -1
- package/lib/tui-compat-extensions.js +3 -3
- package/lib/tui-compat-plugin-host.js +5 -5
- package/lib/tui-compat-scenes.js +1 -1
- package/lib/tui-compat-settings-sections.js +1 -1
- package/lib/tui-compat-working-activity.js +4 -4
- package/lib/tui-compat-workspaces.js +1 -1
- package/lib/tui-compat.js +9 -26
- package/lib/types/args.d.ts +2 -0
- package/lib/types/permission-bypass.d.ts +16 -0
- package/lib/types/profile-boot.d.ts +12 -0
- package/lib/types/tui-compat.d.ts +4 -0
- package/lib/types/web-browser.d.ts +6 -0
- package/lib/types/web-instance.d.ts +42 -0
- package/lib/web-browser-Vcgfdsv_.js +46 -0
- package/lib/web-instance-B0jec7cq.js +2 -0
- package/lib/web-instance-DbP7IHPv.js +280 -0
- package/lib/{workspaces-Bb3kCAFb.js → workspaces-XY7UdfXp.js} +2 -2
- package/package.json +34 -32
- package/runtime/manifest.json +4 -2
- package/runtime/node_modules/@deepseek-ai/dsh-agent-default-model/lib/index.js +34 -4
- package/runtime/node_modules/@deepseek-ai/dsh-agent-default-model/lib/types/index.d.ts +9 -0
- package/runtime/node_modules/@deepseek-ai/dsh-agent-preset/skills/cordis-composition-reference/references/packages.md +2 -1
- package/runtime/node_modules/@deepseek-ai/dsh-api-gateway/lib/client.js +2 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-gateway/lib/types/client/remote-stream.js +4 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-remotes/lib/client.js +39 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/index.js +6 -1
- package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/typert.host.js +10 -3
- package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/typert.remote-client.js +7 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/catalog.js +8 -1
- package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/types.d.ts +10 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-settings-controller/lib/index.js +28 -1
- package/runtime/node_modules/@deepseek-ai/dsh-api-settings-controller/lib/typert.host.js +39 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-settings-controller/lib/typert.remote-client.d.ts +2 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-settings-controller/lib/typert.remote-client.js +32 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-settings-controller/lib/types/index.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-settings-controller/lib/types/index.js +19 -1
- package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/index.js +28 -0
- package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/feed.js +17 -0
- package/runtime/node_modules/@deepseek-ai/dsh-app-boot/lib/index.js +67 -19
- package/runtime/node_modules/@deepseek-ai/dsh-app-boot/lib/types/index.d.ts +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-app-boot/lib/types/profile-context.d.ts +11 -1
- package/runtime/node_modules/@deepseek-ai/dsh-app-boot/lib/worker/profile-resolution-bootstrap.js +22 -3
- package/runtime/node_modules/@deepseek-ai/dsh-base/cordis.patch.yml +9 -3
- package/runtime/node_modules/@deepseek-ai/dsh-base/package.json +2 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-chat/lib/client.js +3 -2
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-chat/lib/types/index.d.ts +2 -2
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-goal/lib/client.js +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/client.js +60 -12
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/types/client/directory.d.ts +3 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/types/client/locales.d.ts +6 -0
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/markdown/CodeBlock.module.css +3 -5
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/markdown/MarkdownText.module.css +23 -27
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings/lib/types/client/contract/slots.d.ts +3 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-account/lib/client.js +2 -2
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-account/lib/types/index.d.ts +2 -2
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +8 -7
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/types/client/SettingsDocumentAction.d.ts +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/types/client/settings-document-store.d.ts +2 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-models/lib/client.js +33 -12
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-models/lib/types/client/locales.d.ts +2 -0
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-plugin-inventory/lib/client.js +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-theme/lib/client.js +1 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/client.js +8 -1
- package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/locales.d.ts +2 -0
- package/runtime/node_modules/@deepseek-ai/dsh-computer-use/lib/index.js +35 -0
- package/runtime/node_modules/@deepseek-ai/dsh-computer-use/lib/types/brand.d.ts +11 -0
- package/runtime/node_modules/@deepseek-ai/dsh-computer-use/lib/types/brand.js +10 -0
- package/runtime/node_modules/@deepseek-ai/dsh-computer-use/lib/types/index.d.ts +28 -0
- package/runtime/node_modules/@deepseek-ai/dsh-computer-use/lib/types/index.js +36 -0
- package/runtime/node_modules/@deepseek-ai/dsh-computer-use/package.json +42 -0
- package/runtime/node_modules/@deepseek-ai/dsh-config-editor/lib/index.js +102 -14
- package/runtime/node_modules/@deepseek-ai/dsh-config-editor/lib/types/index.d.ts +34 -4
- package/runtime/node_modules/@deepseek-ai/dsh-config-editor/package.json +1 -0
- package/runtime/node_modules/@deepseek-ai/dsh-host-webserver/lib/index.js +2 -1
- package/runtime/node_modules/@deepseek-ai/dsh-native-command/lib/index.js +21 -8
- package/runtime/node_modules/@deepseek-ai/dsh-native-command/lib/types/path-opener.d.ts +4 -6
- package/runtime/node_modules/@deepseek-ai/dsh-native-command/lib/types/path-opener.js +18 -7
- package/runtime/node_modules/@deepseek-ai/dsh-settings/lib/index.js +5 -4
- package/runtime/node_modules/@deepseek-ai/dsh-settings/lib/types/index.d.ts +4 -3
- package/runtime/node_modules/@deepseek-ai/dsh-settings/lib/types/index.js +4 -3
- package/runtime/node_modules/@deepseek-ai/dsh-storage/lib/types/backend.d.ts +4 -0
- package/runtime/node_modules/@deepseek-ai/dsh-storage-domain/lib/index.js +60 -0
- package/runtime/node_modules/@deepseek-ai/dsh-storage-domain/lib/types/domain.d.ts +15 -0
- package/runtime/node_modules/@deepseek-ai/dsh-storage-json/lib/index.js +25 -0
- package/runtime/node_modules/@deepseek-ai/dsh-storage-json/package.json +1 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/index.js +5472 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/command-args.d.ts +17 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/commands/tianti/cicd.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/commands/tianti/tianti.d.ts +7 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/commands/tianti-deliver/tianti-deliver.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/config.d.ts +27 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/index.d.ts +12 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/memory-config.d.ts +10 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/runtime.d.ts +105 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/aliases.d.ts +11 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/auth.d.ts +20 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/client.d.ts +206 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/config.d.ts +20 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/index.d.ts +6 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/log.d.ts +16 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/types.d.ts +136 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/codebase.d.ts +19 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/dynamicWorkflow.d.ts +10 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/engine.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/executor.d.ts +11 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/executors.d.ts +15 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/index.d.ts +23 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/memorySuggestions.d.ts +12 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/openspec.d.ts +31 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/recommend.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/render.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/requirementContext.d.ts +12 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/scenarioUpgrade.d.ts +24 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/state.d.ts +16 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/store.d.ts +33 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/templates.d.ts +8 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/types.d.ts +385 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/wanmaDevelopmentWorkflow/writeReviewSync.d.ts +6 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/writeReview/index.d.ts +29 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/writeReview/store.d.ts +20 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/writeReview/types.d.ts +49 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/tools.d.ts +12 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tianti/package.json +55 -0
- package/runtime/node_modules/@deepseek-ai/dsh-tool-cordis/lib/types/api-catalog.js +57 -13
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/assets/index-D5kpKf--.css +1 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/assets/index-Dz_cDcce.js +133 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/assets/index-Dz_cDcce.js.map +1 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/chatcode-apple-touch-icon.png +0 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/chatcode-icon-32.png +0 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/chatcode-mask-icon.svg +1 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/favicon-dark.svg +1 -3
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/favicon.ico +0 -0
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/favicon.svg +1 -3
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/index.html +8 -4
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/preview.html +8 -4
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/package.json +2 -1
- package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/index.js +110 -33
- package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/entity.d.ts +6 -0
- package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/entity.js +23 -17
- package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/index.d.ts +10 -0
- package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/index.js +95 -21
- package/lib/dump-config-CijLv_kn.js +0 -2
- package/lib/tuiDisplayPrefs-Dm_fULiR.js +0 -204
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/assets/index-DUvMhLle.css +0 -1
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/assets/index-Q6zc2uHV.js +0 -127
- package/runtime/node_modules/@deepseek-ai/dsh-web-frontend/dist/assets/index-Q6zc2uHV.js.map +0 -1
|
@@ -2,28 +2,77 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { isDeepStrictEqual } from "node:util";
|
|
4
4
|
import { Service, resolveConfig } from "@deepseek-ai/cordis";
|
|
5
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
6
|
import { entryListSchema } from "@deepseek-ai/cordis-plugin-include";
|
|
6
7
|
import yaml from "js-yaml";
|
|
7
|
-
import { composeEntries, loadProfileDirectory, readProfilePatches, reconcileProfilePatches } from "@deepseek-ai/dsh-app-boot";
|
|
8
|
+
import { composeEntries, loadOptionalPatches, loadProfileDirectory, readProfilePatches, reconcileProfilePatches } from "@deepseek-ai/dsh-app-boot";
|
|
8
9
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
9
10
|
import { Scalar, isMap, isSeq, parseDocument, visit } from "yaml";
|
|
10
11
|
//#region lib/types/index.js
|
|
11
|
-
/** Profile
|
|
12
|
+
/** Profile and shared home configuration edits, serialized with Loader hot reload. */
|
|
12
13
|
function flatten(rows) {
|
|
13
14
|
return rows.flatMap((row) => [row, ...row.group && Array.isArray(row.config) ? flatten(row.config) : []]);
|
|
14
15
|
}
|
|
15
16
|
/** Persist complete raw configs and apply them through the normal Loader path. */
|
|
16
17
|
var ConfigEditor = class extends Service {
|
|
17
18
|
ownerContext;
|
|
19
|
+
config;
|
|
18
20
|
static inject = ["loader", "profileContext"];
|
|
19
|
-
|
|
21
|
+
/** Entry ids whose configuration is shared by all profiles in the same home. */
|
|
22
|
+
static Config = z.object({ sharedEntries: z.array(String).default([]) });
|
|
23
|
+
homeFields = /* @__PURE__ */ new Map();
|
|
24
|
+
constructor(ownerContext, config) {
|
|
20
25
|
super(ownerContext, "configEditor");
|
|
21
26
|
this.ownerContext = ownerContext;
|
|
27
|
+
this.config = config;
|
|
22
28
|
}
|
|
23
29
|
/** The profile patch edited by this service. */
|
|
24
30
|
get documentPath() {
|
|
25
31
|
return this.ownerContext.profileContext.patchPath;
|
|
26
32
|
}
|
|
33
|
+
shared(entry) {
|
|
34
|
+
return this.config.sharedEntries.includes(entry.options.id) || entry.fiber !== void 0 && this.homeFields.has(entry.fiber);
|
|
35
|
+
}
|
|
36
|
+
pathFor(entry) {
|
|
37
|
+
return this.shared(entry) ? join(this.ownerContext.profileContext.home, "cordis.patch.yml") : this.documentPath;
|
|
38
|
+
}
|
|
39
|
+
/** Materialize the document that owns a namespace, or the profile patch when no namespace is given.
|
|
40
|
+
* @param namespace Registered entry id; omitted selects the profile patch.
|
|
41
|
+
* @returns Existing owning document path; missing documents are initialized under the writer lock.
|
|
42
|
+
* @throws Error when the namespace is unavailable or file preparation fails.
|
|
43
|
+
*/
|
|
44
|
+
async prepareDocument(namespace) {
|
|
45
|
+
const entry = namespace === void 0 ? void 0 : this.entries().find((candidate) => candidate.options.id === namespace);
|
|
46
|
+
if (namespace !== void 0 && entry === void 0) throw new Error(`Configuration namespace "${namespace}" is not available`);
|
|
47
|
+
const path = entry === void 0 ? this.documentPath : this.pathFor(entry);
|
|
48
|
+
await withFileLock(entry !== void 0 && this.shared(entry) ? path : join(this.ownerContext.profileContext.dir, "package.json"), async () => {
|
|
49
|
+
try {
|
|
50
|
+
await readFile(path, "utf8");
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code !== "ENOENT") throw error;
|
|
53
|
+
await writeFileAtomic(path, "[]\n", { mode: 384 });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
/** Persist selected top-level fields in the home patch shared by profiles.
|
|
59
|
+
* @param fields Plugin-owned fields to save at user scope.
|
|
60
|
+
* @param owner Activation whose Loader entry owns these fields.
|
|
61
|
+
* @returns Disposer restoring profile-scoped editing for this activation.
|
|
62
|
+
*/
|
|
63
|
+
configureHome(fields, owner) {
|
|
64
|
+
if (this.homeFields.has(owner)) throw new Error("Home fields are already configured for this plugin instance");
|
|
65
|
+
this.homeFields.set(owner, [...fields]);
|
|
66
|
+
return () => {
|
|
67
|
+
this.homeFields.delete(owner);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
homePath() {
|
|
71
|
+
return join(this.ownerContext.profileContext.home, "cordis.patch.yml");
|
|
72
|
+
}
|
|
73
|
+
userPatches(entry, loaded) {
|
|
74
|
+
return this.shared(entry) ? loadOptionalPatches("dsh", this.homePath()) ?? [] : loaded.patches;
|
|
75
|
+
}
|
|
27
76
|
/** Addressable profile rows; nested Includes have independent configuration ownership.
|
|
28
77
|
* @returns Active entries with unique profile patch ids.
|
|
29
78
|
*/
|
|
@@ -33,7 +82,7 @@ var ConfigEditor = class extends Service {
|
|
|
33
82
|
for (const entry of candidates) counts.set(entry.options.id, (counts.get(entry.options.id) ?? 0) + 1);
|
|
34
83
|
return candidates.filter((entry) => counts.get(entry.options.id) === 1);
|
|
35
84
|
}
|
|
36
|
-
/** Read inherited and explicit
|
|
85
|
+
/** Read inherited and explicit values from each entry's selected write layer.
|
|
37
86
|
* @returns Detached layer values alongside their Loader entries.
|
|
38
87
|
*/
|
|
39
88
|
configuration() {
|
|
@@ -42,17 +91,26 @@ var ConfigEditor = class extends Service {
|
|
|
42
91
|
return this.entries().map((entry) => ({
|
|
43
92
|
entry,
|
|
44
93
|
inherited: this.inherited(entry, loaded),
|
|
45
|
-
override: structuredClone(loaded.
|
|
94
|
+
override: structuredClone(this.userPatches(entry, loaded).findLast((row) => row.id === entry.options.id && row.config !== void 0)?.config ?? {})
|
|
46
95
|
}));
|
|
47
96
|
}
|
|
48
97
|
inherited(entry, loaded) {
|
|
49
|
-
const
|
|
98
|
+
const fields = entry.fiber === void 0 ? void 0 : this.homeFields.get(entry.fiber);
|
|
99
|
+
const patches = this.userPatches(entry, loaded).map((patch) => {
|
|
50
100
|
if (patch.id !== entry.options.id || patch.insert !== void 0) return patch;
|
|
101
|
+
if (fields !== void 0) {
|
|
102
|
+
const config = { ...patch.config };
|
|
103
|
+
for (const field of fields) Reflect.deleteProperty(config, field);
|
|
104
|
+
return {
|
|
105
|
+
...patch,
|
|
106
|
+
config
|
|
107
|
+
};
|
|
108
|
+
}
|
|
51
109
|
const rest = { ...patch };
|
|
52
110
|
Reflect.deleteProperty(rest, "config");
|
|
53
111
|
return rest;
|
|
54
112
|
});
|
|
55
|
-
const row = flatten(composeEntries([...loaded.layers.map((layer) => layer.patches), patches])).find((row) => row.id === entry.options.id);
|
|
113
|
+
const row = flatten(composeEntries(this.shared(entry) ? [readProfilePatches("dsh", this.ownerContext.profileContext, loaded, patches)] : [...loaded.layers.map((layer) => layer.patches), patches])).find((row) => row.id === entry.options.id);
|
|
56
114
|
return structuredClone(row?.config ?? {});
|
|
57
115
|
}
|
|
58
116
|
/** Validate, persist, and reconcile a plugin's next config; ordinary fields keep normal lifecycle rules.
|
|
@@ -62,15 +120,23 @@ var ConfigEditor = class extends Service {
|
|
|
62
120
|
*/
|
|
63
121
|
async edit(entry, change) {
|
|
64
122
|
const run = async () => {
|
|
65
|
-
const
|
|
66
|
-
|
|
123
|
+
const fields = entry.fiber === void 0 ? void 0 : this.homeFields.get(entry.fiber);
|
|
124
|
+
const shared = this.shared(entry);
|
|
125
|
+
const path = this.pathFor(entry);
|
|
126
|
+
const lockPath = shared ? this.homePath() : join(this.ownerContext.profileContext.dir, "package.json");
|
|
127
|
+
const persist = () => withFileLock(lockPath, async () => {
|
|
67
128
|
if (!this.entries().includes(entry) || entry.fiber === void 0) throw new Error("Configuration entry is no longer available");
|
|
68
129
|
const beforePatches = readProfilePatches("dsh", this.ownerContext.profileContext);
|
|
69
130
|
await reconcileProfilePatches(this.ownerContext.root, beforePatches, "dsh");
|
|
70
131
|
if (!this.entries().includes(entry)) throw new Error("Configuration entry changed during reload");
|
|
71
132
|
const current = structuredClone(entry.options.config ?? {});
|
|
72
|
-
const
|
|
133
|
+
const profileContext = this.ownerContext.profileContext;
|
|
134
|
+
const inherited = this.inherited(entry, loadProfileDirectory("dsh", profileContext.dir, profileContext.installAnchor));
|
|
73
135
|
const next = change(current, inherited);
|
|
136
|
+
if (fields !== void 0 && Object.keys({
|
|
137
|
+
...current,
|
|
138
|
+
...next
|
|
139
|
+
}).some((key) => !fields.includes(key) && !isDeepStrictEqual(current[key], next[key]))) throw new Error("Home-scoped edits may only change the registered fields");
|
|
74
140
|
const fiber = entry.fiber;
|
|
75
141
|
if (fiber.state !== 2) throw new Error("Configuration plugin is no longer active");
|
|
76
142
|
const resolved = fiber.ctx.waterfall(fiber, "internal/config", next, () => next);
|
|
@@ -90,7 +156,25 @@ var ConfigEditor = class extends Service {
|
|
|
90
156
|
if (!isSeq(document.contents)) throw new Error("Profile patch must be a YAML sequence");
|
|
91
157
|
document.contents.flow = false;
|
|
92
158
|
const index = document.contents.items.findLastIndex((item, index) => isMap(item) && document.getIn([index, "id"]) === entry.options.id && !item.has("insert") && (!item.has("name") || document.getIn([index, "name"]) === entry.options.name));
|
|
93
|
-
if (
|
|
159
|
+
if (fields !== void 0) {
|
|
160
|
+
if (index < 0) document.add(document.createNode({
|
|
161
|
+
id: entry.options.id,
|
|
162
|
+
config: {}
|
|
163
|
+
}));
|
|
164
|
+
const target = index < 0 ? document.contents.items.length - 1 : index;
|
|
165
|
+
document.setIn([target, "mergeConfig"], true);
|
|
166
|
+
if (!isMap(document.getIn([target, "config"], true))) document.setIn([target, "config"], document.createNode({}));
|
|
167
|
+
for (const field of fields) if (Object.hasOwn(next, field)) document.setIn([
|
|
168
|
+
target,
|
|
169
|
+
"config",
|
|
170
|
+
field
|
|
171
|
+
], document.createNode(next[field]));
|
|
172
|
+
else document.deleteIn([
|
|
173
|
+
target,
|
|
174
|
+
"config",
|
|
175
|
+
field
|
|
176
|
+
]);
|
|
177
|
+
} else if (!shared && isDeepStrictEqual(next, inherited)) for (let index = document.contents.items.length - 1; index >= 0; index--) {
|
|
94
178
|
const row = document.contents.items[index];
|
|
95
179
|
if (!isMap(row) || document.getIn([index, "id"]) !== entry.options.id || row.has("insert")) continue;
|
|
96
180
|
row.delete("config");
|
|
@@ -109,9 +193,11 @@ var ConfigEditor = class extends Service {
|
|
|
109
193
|
return expression;
|
|
110
194
|
} });
|
|
111
195
|
const profile = this.ownerContext.profileContext;
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
196
|
+
const loaded = loadProfileDirectory("dsh", profile.dir, profile.installAnchor);
|
|
197
|
+
const candidate = yaml.load(String(document), { schema: entryListSchema });
|
|
198
|
+
const patches = shared ? readProfilePatches("dsh", profile, loaded, candidate) : readProfilePatches("dsh", profile, {
|
|
199
|
+
...loaded,
|
|
200
|
+
patches: candidate
|
|
115
201
|
});
|
|
116
202
|
if (!isDeepStrictEqual(flatten(composeEntries([patches])).find((row) => row.id === entry.options.id)?.config ?? {}, next)) throw new Error(`Configuration for "${entry.options.id}" is overridden by a home patch or command-line overlay`);
|
|
117
203
|
await writeFileAtomic(path, String(document), { mode: 384 });
|
|
@@ -123,6 +209,8 @@ var ConfigEditor = class extends Service {
|
|
|
123
209
|
throw error;
|
|
124
210
|
}
|
|
125
211
|
});
|
|
212
|
+
if (this.shared(entry)) await withFileLock(join(this.ownerContext.profileContext.dir, "package.json"), persist);
|
|
213
|
+
else await persist();
|
|
126
214
|
};
|
|
127
215
|
const hmr = this.ownerContext.get("hmr");
|
|
128
216
|
await (hmr === void 0 ? run() : hmr.runExclusive(run));
|
|
@@ -1,23 +1,53 @@
|
|
|
1
|
-
import { Context, Service } from '@deepseek-ai/cordis';
|
|
1
|
+
import { Context, Service, type Fiber } from '@deepseek-ai/cordis';
|
|
2
|
+
import z from '@deepseek-ai/schemastery';
|
|
2
3
|
import type { Entry } from '@deepseek-ai/cordis-plugin-loader';
|
|
3
4
|
declare module '@deepseek-ai/cordis' {
|
|
4
5
|
interface Context {
|
|
5
|
-
/** Persistent edits
|
|
6
|
+
/** Persistent plugin edits in profile patches or registered shared home fields. */
|
|
6
7
|
configEditor: ConfigEditor;
|
|
7
8
|
}
|
|
8
9
|
}
|
|
10
|
+
/** Entries whose complete configuration is persisted in the shared home patch. */
|
|
11
|
+
export interface ConfigEditorConfig {
|
|
12
|
+
/** Entry ids whose complete configuration is shared across profiles. */
|
|
13
|
+
sharedEntries: string[];
|
|
14
|
+
}
|
|
9
15
|
/** Persist complete raw configs and apply them through the normal Loader path. */
|
|
10
16
|
export declare class ConfigEditor extends Service {
|
|
11
17
|
private readonly ownerContext;
|
|
18
|
+
private readonly config;
|
|
12
19
|
static inject: string[];
|
|
13
|
-
|
|
20
|
+
/** Entry ids whose configuration is shared by all profiles in the same home. */
|
|
21
|
+
static Config: z<Schemastery.ObjectS<NoInfer<{
|
|
22
|
+
sharedEntries: z<string[], string[], "defined">;
|
|
23
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
24
|
+
sharedEntries: z<string[], string[], "defined">;
|
|
25
|
+
}>>, "plain">;
|
|
26
|
+
private readonly homeFields;
|
|
27
|
+
constructor(ownerContext: Context, config: ConfigEditorConfig);
|
|
14
28
|
/** The profile patch edited by this service. */
|
|
15
29
|
get documentPath(): string;
|
|
30
|
+
private shared;
|
|
31
|
+
private pathFor;
|
|
32
|
+
/** Materialize the document that owns a namespace, or the profile patch when no namespace is given.
|
|
33
|
+
* @param namespace Registered entry id; omitted selects the profile patch.
|
|
34
|
+
* @returns Existing owning document path; missing documents are initialized under the writer lock.
|
|
35
|
+
* @throws Error when the namespace is unavailable or file preparation fails.
|
|
36
|
+
*/
|
|
37
|
+
prepareDocument(namespace?: string): Promise<string>;
|
|
38
|
+
/** Persist selected top-level fields in the home patch shared by profiles.
|
|
39
|
+
* @param fields Plugin-owned fields to save at user scope.
|
|
40
|
+
* @param owner Activation whose Loader entry owns these fields.
|
|
41
|
+
* @returns Disposer restoring profile-scoped editing for this activation.
|
|
42
|
+
*/
|
|
43
|
+
configureHome(fields: readonly string[], owner: Fiber): () => void;
|
|
44
|
+
private homePath;
|
|
45
|
+
private userPatches;
|
|
16
46
|
/** Addressable profile rows; nested Includes have independent configuration ownership.
|
|
17
47
|
* @returns Active entries with unique profile patch ids.
|
|
18
48
|
*/
|
|
19
49
|
entries(): Entry[];
|
|
20
|
-
/** Read inherited and explicit
|
|
50
|
+
/** Read inherited and explicit values from each entry's selected write layer.
|
|
21
51
|
* @returns Detached layer values alongside their Loader entries.
|
|
22
52
|
*/
|
|
23
53
|
configuration(): Array<{
|
|
@@ -61,8 +61,9 @@ function splice(html, at, markup) {
|
|
|
61
61
|
* applies the table asynchronously installs it ahead of the entry module and
|
|
62
62
|
* settles it after the last row; the served form below creates and resolves
|
|
63
63
|
* it in one statement, because every row is already in the document text.
|
|
64
|
+
* This classic script precedes browser polyfills, so it uses the base Promise API.
|
|
64
65
|
*/
|
|
65
|
-
const READY_MARKUP = "<script>(globalThis.__DSH_BOOT_READY__ ??= Promise.
|
|
66
|
+
const READY_MARKUP = "<script>(globalThis.__DSH_BOOT_READY__ ??= { promise: Promise.resolve(), resolve() {}, reject() {} }).resolve()<\/script>";
|
|
66
67
|
/**
|
|
67
68
|
* Render rows into an index.html body: head rows immediately after the
|
|
68
69
|
* opening head tag, body rows immediately after the opening body tag, each
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import { homedir, release, tmpdir } from "node:os";
|
|
3
3
|
import { dirname, extname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -44,10 +44,8 @@ const runNativeCommand = (command, args, signal) => new Promise((resolve, reject
|
|
|
44
44
|
* The default intent prefers the default browser for documents it renders when
|
|
45
45
|
* the platform can name one, then falls back to the default application. WSL
|
|
46
46
|
* translates every path for the Windows desktop instead of assuming a Linux
|
|
47
|
-
* GUI. The text-editor intent never consults the browser. Windows
|
|
48
|
-
*
|
|
49
|
-
* a double-click uses, selects the application, while a process that resolves
|
|
50
|
-
* the association itself reads a narrower record and reports none.
|
|
47
|
+
* GUI. The text-editor intent never consults the browser. Windows launches
|
|
48
|
+
* Notepad for text editing and delegates other intents to Explorer.
|
|
51
49
|
* @module @deepseek-ai/dsh-native-command/path-opener
|
|
52
50
|
*/
|
|
53
51
|
/** Documents a browser renders, as opposed to ones an editor merely edits. */
|
|
@@ -176,7 +174,22 @@ async function openNativePathWithIntent(path, signal, intent, internals = {}) {
|
|
|
176
174
|
return;
|
|
177
175
|
}
|
|
178
176
|
if (platform === "win32") {
|
|
179
|
-
|
|
177
|
+
if (intent === "text-editor" && internals.run === void 0) {
|
|
178
|
+
signal.throwIfAborted();
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
const editor = spawn("notepad.exe", [path], {
|
|
181
|
+
detached: true,
|
|
182
|
+
stdio: "ignore",
|
|
183
|
+
windowsHide: false
|
|
184
|
+
});
|
|
185
|
+
editor.once("error", reject);
|
|
186
|
+
editor.once("spawn", () => {
|
|
187
|
+
editor.unref();
|
|
188
|
+
resolve();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
} else if (intent === "text-editor") await run("notepad.exe", [path], signal);
|
|
192
|
+
else await openWindowsPath(path, signal, run);
|
|
180
193
|
return;
|
|
181
194
|
}
|
|
182
195
|
if (platform === "linux") {
|
|
@@ -228,8 +241,8 @@ function openNativeAssociatedPath(path, signal, internals = {}) {
|
|
|
228
241
|
return openNativePathWithIntent(path, signal, "association", internals);
|
|
229
242
|
}
|
|
230
243
|
/**
|
|
231
|
-
* Open a text document for editing
|
|
232
|
-
*
|
|
244
|
+
* Open a text document for editing. macOS uses the text editor; Windows launches
|
|
245
|
+
* Notepad independently of file associations and RPC lifetime.
|
|
233
246
|
* @param path - absolute or host-resolvable text-document path.
|
|
234
247
|
* @param signal - caller/connection lifetime; abort terminates the native command.
|
|
235
248
|
* @param internals - Platform and runner hooks for deterministic tests.
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
* The default intent prefers the default browser for documents it renders when
|
|
6
6
|
* the platform can name one, then falls back to the default application. WSL
|
|
7
7
|
* translates every path for the Windows desktop instead of assuming a Linux
|
|
8
|
-
* GUI. The text-editor intent never consults the browser. Windows
|
|
9
|
-
*
|
|
10
|
-
* a double-click uses, selects the application, while a process that resolves
|
|
11
|
-
* the association itself reads a narrower record and reports none.
|
|
8
|
+
* GUI. The text-editor intent never consults the browser. Windows launches
|
|
9
|
+
* Notepad for text editing and delegates other intents to Explorer.
|
|
12
10
|
* @module @deepseek-ai/dsh-native-command/path-opener
|
|
13
11
|
*/
|
|
14
12
|
import { type NativeCommandRunner } from './runner.ts';
|
|
@@ -52,8 +50,8 @@ export declare function openNativePath(path: string, signal: AbortSignal, intern
|
|
|
52
50
|
*/
|
|
53
51
|
export declare function openNativeAssociatedPath(path: string, signal: AbortSignal, internals?: PathOpenerInternals): Promise<void>;
|
|
54
52
|
/**
|
|
55
|
-
* Open a text document for editing
|
|
56
|
-
*
|
|
53
|
+
* Open a text document for editing. macOS uses the text editor; Windows launches
|
|
54
|
+
* Notepad independently of file associations and RPC lifetime.
|
|
57
55
|
* @param path - absolute or host-resolvable text-document path.
|
|
58
56
|
* @param signal - caller/connection lifetime; abort terminates the native command.
|
|
59
57
|
* @param internals - Platform and runner hooks for deterministic tests.
|
|
@@ -5,15 +5,14 @@
|
|
|
5
5
|
* The default intent prefers the default browser for documents it renders when
|
|
6
6
|
* the platform can name one, then falls back to the default application. WSL
|
|
7
7
|
* translates every path for the Windows desktop instead of assuming a Linux
|
|
8
|
-
* GUI. The text-editor intent never consults the browser. Windows
|
|
9
|
-
*
|
|
10
|
-
* a double-click uses, selects the application, while a process that resolves
|
|
11
|
-
* the association itself reads a narrower record and reports none.
|
|
8
|
+
* GUI. The text-editor intent never consults the browser. Windows launches
|
|
9
|
+
* Notepad for text editing and delegates other intents to Explorer.
|
|
12
10
|
* @module @deepseek-ai/dsh-native-command/path-opener
|
|
13
11
|
*/
|
|
14
12
|
import { release as osRelease } from 'node:os';
|
|
15
13
|
import { dirname, extname } from 'node:path';
|
|
16
14
|
import { pathToFileURL } from 'node:url';
|
|
15
|
+
import { spawn } from 'node:child_process';
|
|
17
16
|
import { runNativeCommand } from "./runner.js";
|
|
18
17
|
/** Documents a browser renders, as opposed to ones an editor merely edits. */
|
|
19
18
|
const BROWSER_DOCUMENTS = new Set(['.html', '.htm', '.xhtml', '.svg']);
|
|
@@ -154,7 +153,19 @@ async function openNativePathWithIntent(path, signal, intent, internals = {}) {
|
|
|
154
153
|
return;
|
|
155
154
|
}
|
|
156
155
|
if (platform === 'win32') {
|
|
157
|
-
|
|
156
|
+
if (intent === 'text-editor' && internals.run === undefined) {
|
|
157
|
+
signal.throwIfAborted();
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
// The editor outlives the RPC. Do not wait for exit or kill an open document on disconnect.
|
|
160
|
+
const editor = spawn('notepad.exe', [path], { detached: true, stdio: 'ignore', windowsHide: false });
|
|
161
|
+
editor.once('error', reject);
|
|
162
|
+
editor.once('spawn', () => { editor.unref(); resolve(); });
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else if (intent === 'text-editor')
|
|
166
|
+
await run('notepad.exe', [path], signal);
|
|
167
|
+
else
|
|
168
|
+
await openWindowsPath(path, signal, run);
|
|
158
169
|
return;
|
|
159
170
|
}
|
|
160
171
|
if (platform === 'linux') {
|
|
@@ -208,8 +219,8 @@ export function openNativeAssociatedPath(path, signal, internals = {}) {
|
|
|
208
219
|
return openNativePathWithIntent(path, signal, 'association', internals);
|
|
209
220
|
}
|
|
210
221
|
/**
|
|
211
|
-
* Open a text document for editing
|
|
212
|
-
*
|
|
222
|
+
* Open a text document for editing. macOS uses the text editor; Windows launches
|
|
223
|
+
* Notepad independently of file associations and RPC lifetime.
|
|
213
224
|
* @param path - absolute or host-resolvable text-document path.
|
|
214
225
|
* @param signal - caller/connection lifetime; abort terminates the native command.
|
|
215
226
|
* @param internals - Platform and runner hooks for deterministic tests.
|
|
@@ -402,11 +402,12 @@ var SettingsForms = class extends Service {
|
|
|
402
402
|
get documentPath() {
|
|
403
403
|
return this.ownerContext.configEditor.documentPath;
|
|
404
404
|
}
|
|
405
|
-
/** Locate the
|
|
406
|
-
* @
|
|
405
|
+
/** Locate and materialize the owning patch for native editing.
|
|
406
|
+
* @param namespace Optional namespace; shared entries resolve to the home-level document.
|
|
407
|
+
* @returns The existing owning patch path.
|
|
407
408
|
*/
|
|
408
|
-
prepareDocument() {
|
|
409
|
-
return
|
|
409
|
+
prepareDocument(namespace) {
|
|
410
|
+
return this.ownerContext.configEditor.prepareDocument(namespace);
|
|
410
411
|
}
|
|
411
412
|
/** Read active plugin schemas and their live values.
|
|
412
413
|
* @param options Redaction required for remote callers.
|
|
@@ -85,10 +85,11 @@ export declare class SettingsForms extends Service {
|
|
|
85
85
|
get writable(): boolean;
|
|
86
86
|
/** Current profile patch shown by the native configuration editor. */
|
|
87
87
|
get documentPath(): string;
|
|
88
|
-
/** Locate the
|
|
89
|
-
* @
|
|
88
|
+
/** Locate and materialize the owning patch for native editing.
|
|
89
|
+
* @param namespace Optional namespace; shared entries resolve to the home-level document.
|
|
90
|
+
* @returns The existing owning patch path.
|
|
90
91
|
*/
|
|
91
|
-
prepareDocument(): Promise<string>;
|
|
92
|
+
prepareDocument(namespace?: string): Promise<string>;
|
|
92
93
|
/** Read active plugin schemas and their live values.
|
|
93
94
|
* @param options Redaction required for remote callers.
|
|
94
95
|
* @returns Forms keyed by unique profile entry ids.
|
|
@@ -261,10 +261,11 @@ export class SettingsForms extends Service {
|
|
|
261
261
|
get writable() { return true; }
|
|
262
262
|
/** Current profile patch shown by the native configuration editor. */
|
|
263
263
|
get documentPath() { return this.ownerContext.configEditor.documentPath; }
|
|
264
|
-
/** Locate the
|
|
265
|
-
* @
|
|
264
|
+
/** Locate and materialize the owning patch for native editing.
|
|
265
|
+
* @param namespace Optional namespace; shared entries resolve to the home-level document.
|
|
266
|
+
* @returns The existing owning patch path.
|
|
266
267
|
*/
|
|
267
|
-
prepareDocument() { return
|
|
268
|
+
prepareDocument(namespace) { return this.ownerContext.configEditor.prepareDocument(namespace); }
|
|
268
269
|
/** Read active plugin schemas and their live values.
|
|
269
270
|
* @param options Redaction required for remote callers.
|
|
270
271
|
* @returns Forms keyed by unique profile entry ids.
|
|
@@ -86,6 +86,10 @@ export interface KvUnit {
|
|
|
86
86
|
tables: Record<string, Record<string, unknown>>;
|
|
87
87
|
global: unknown;
|
|
88
88
|
}>;
|
|
89
|
+
/** Reload committed medium state before a domain refresh. Unsupported backends retain their snapshot. */
|
|
90
|
+
refresh?(): Promise<void>;
|
|
91
|
+
/** Hold a cross-process writer lock across a complete logical operation, including all its writes. */
|
|
92
|
+
exclusive?<T>(operation: () => Promise<T>): Promise<T>;
|
|
89
93
|
/**
|
|
90
94
|
* Read one record without loading other records. Backends without keyed
|
|
91
95
|
* reads omit this member; on-demand domains require it.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { UNIT_NAME_RE, storageBackendServiceKey } from "@deepseek-ai/dsh-storage";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
3
5
|
//#region lib/types/error.js
|
|
4
6
|
/**
|
|
5
7
|
* Error vocabulary of the domain data form.
|
|
@@ -109,6 +111,7 @@ const noop = () => {};
|
|
|
109
111
|
*/
|
|
110
112
|
var DomainImpl = class {
|
|
111
113
|
ctx;
|
|
114
|
+
spec;
|
|
112
115
|
unit;
|
|
113
116
|
onClosed;
|
|
114
117
|
/** Domain name from the spec. */
|
|
@@ -123,6 +126,7 @@ var DomainImpl = class {
|
|
|
123
126
|
/** Set when close finishes (chain drained, unit closed): reads reject from here on. */
|
|
124
127
|
closed = false;
|
|
125
128
|
disposal;
|
|
129
|
+
operationScope = new AsyncLocalStorage();
|
|
126
130
|
/**
|
|
127
131
|
* @param ctx - Context that carries `domain/changed` emissions.
|
|
128
132
|
* @param spec - The domain declaration.
|
|
@@ -138,6 +142,7 @@ var DomainImpl = class {
|
|
|
138
142
|
*/
|
|
139
143
|
constructor(ctx, spec, unit, records, globalValue, onClosed, readStored) {
|
|
140
144
|
this.ctx = ctx;
|
|
145
|
+
this.spec = spec;
|
|
141
146
|
this.unit = unit;
|
|
142
147
|
this.onClosed = onClosed;
|
|
143
148
|
this.name = spec.name;
|
|
@@ -191,6 +196,53 @@ var DomainImpl = class {
|
|
|
191
196
|
if (table === void 0) throw new Error(`domain '${this.name}' declares no table '${name}'`);
|
|
192
197
|
return table;
|
|
193
198
|
}
|
|
199
|
+
/** Reload eager records atomically in memory; a malformed record leaves the previous projection untouched.
|
|
200
|
+
* @returns Whether persisted records or global values changed the in-memory projection.
|
|
201
|
+
*/
|
|
202
|
+
refresh() {
|
|
203
|
+
return this.enqueue(async () => {
|
|
204
|
+
await this.unit.refresh?.();
|
|
205
|
+
const snapshot = await this.unit.loadAll();
|
|
206
|
+
const records = /* @__PURE__ */ new Map();
|
|
207
|
+
let global;
|
|
208
|
+
try {
|
|
209
|
+
for (const [name, tableSpec] of Object.entries(this.spec.tables)) {
|
|
210
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
211
|
+
for (const [key, raw] of Object.entries(snapshot.tables[name] ?? {})) parsed.set(key, tableSpec.valueSchema.parse(raw));
|
|
212
|
+
records.set(name, parsed);
|
|
213
|
+
}
|
|
214
|
+
global = this.spec.global === void 0 ? void 0 : snapshot.global === null ? this.spec.global.initial : this.spec.global.schema.parse(snapshot.global);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
throw new DomainError("invalid-record", `domain '${this.name}': refreshed snapshot failed validation`, { cause: error });
|
|
217
|
+
}
|
|
218
|
+
let changed = !isDeepStrictEqual(this.globalValue, global);
|
|
219
|
+
for (const [name, next] of records) {
|
|
220
|
+
const table = this.tables.get(name);
|
|
221
|
+
if (table === void 0) throw new Error(`domain '${this.name}' declares no table '${name}'`);
|
|
222
|
+
changed = table.replace(next) || changed;
|
|
223
|
+
}
|
|
224
|
+
this.globalValue = global;
|
|
225
|
+
return changed;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/** Nested table/global operations run on this queue slot, not behind their own awaiting parent.
|
|
229
|
+
* @param operation Work executed under the domain queue and backend writer lock.
|
|
230
|
+
* @returns The operation result after the locked work settles.
|
|
231
|
+
*/
|
|
232
|
+
exclusive(operation) {
|
|
233
|
+
if (this.operationScope.getStore()?.active) return operation();
|
|
234
|
+
return this.enqueue(async () => {
|
|
235
|
+
const run = async () => {
|
|
236
|
+
const scope = { active: true };
|
|
237
|
+
try {
|
|
238
|
+
return await this.operationScope.run(scope, operation);
|
|
239
|
+
} finally {
|
|
240
|
+
scope.active = false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
return await (this.unit.exclusive === void 0 ? run() : this.unit.exclusive(run));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
194
246
|
/**
|
|
195
247
|
* Close this domain: reject new writes immediately, drain already-queued
|
|
196
248
|
* writes (their events still emit), close the unit, then free the name via
|
|
@@ -221,6 +273,7 @@ var DomainImpl = class {
|
|
|
221
273
|
}
|
|
222
274
|
}
|
|
223
275
|
enqueue(job) {
|
|
276
|
+
if (this.operationScope.getStore()?.active) return job();
|
|
224
277
|
if (this.disposing) return Promise.reject(new DomainError("closed", `domain '${this.name}' is closed`));
|
|
225
278
|
const result = this.chain.then(job);
|
|
226
279
|
this.chain = result.then(noop, noop);
|
|
@@ -241,6 +294,13 @@ var KvTableImpl = class {
|
|
|
241
294
|
this.tableName = tableName;
|
|
242
295
|
this.records = records;
|
|
243
296
|
}
|
|
297
|
+
replace(next) {
|
|
298
|
+
if (isDeepStrictEqual(this.records, next)) return false;
|
|
299
|
+
this.records.clear();
|
|
300
|
+
for (const [key, value] of next) this.records.set(key, value);
|
|
301
|
+
this.loaded.clear();
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
244
304
|
read(key) {
|
|
245
305
|
return this.host.enqueue(async () => {
|
|
246
306
|
if (this.host.readStored !== void 0 && !this.loaded.has(key)) {
|
|
@@ -94,6 +94,10 @@ export interface Domain<S extends DomainSpec> {
|
|
|
94
94
|
readonly name: string;
|
|
95
95
|
/** Global singleton handle; a spec without `global` has no usable handle (`never`). */
|
|
96
96
|
readonly global: DomainGlobalHandleOf<S>;
|
|
97
|
+
/** Reload and validate the complete medium snapshot. No events emit; the owner publishes its refreshed projection. */
|
|
98
|
+
refresh(): Promise<boolean>;
|
|
99
|
+
/** Serialize a logical operation locally and, where supported, across processes. Refresh inside the operation before writing. */
|
|
100
|
+
exclusive<T>(operation: () => Promise<T>): Promise<T>;
|
|
97
101
|
/**
|
|
98
102
|
* Resolve one declared table handle. Handles are stable — repeated calls
|
|
99
103
|
* return the same instance.
|
|
@@ -135,6 +139,7 @@ export interface RecordDomain<S extends DomainSpec> {
|
|
|
135
139
|
*/
|
|
136
140
|
export declare class DomainImpl {
|
|
137
141
|
private readonly ctx;
|
|
142
|
+
private readonly spec;
|
|
138
143
|
private readonly unit;
|
|
139
144
|
private readonly onClosed;
|
|
140
145
|
/** Domain name from the spec. */
|
|
@@ -149,6 +154,7 @@ export declare class DomainImpl {
|
|
|
149
154
|
/** Set when close finishes (chain drained, unit closed): reads reject from here on. */
|
|
150
155
|
private closed;
|
|
151
156
|
private disposal?;
|
|
157
|
+
private readonly operationScope;
|
|
152
158
|
/**
|
|
153
159
|
* @param ctx - Context that carries `domain/changed` emissions.
|
|
154
160
|
* @param spec - The domain declaration.
|
|
@@ -172,6 +178,15 @@ export declare class DomainImpl {
|
|
|
172
178
|
* @returns the stable table handle.
|
|
173
179
|
*/
|
|
174
180
|
table(name: string): KvTable<string, unknown>;
|
|
181
|
+
/** Reload eager records atomically in memory; a malformed record leaves the previous projection untouched.
|
|
182
|
+
* @returns Whether persisted records or global values changed the in-memory projection.
|
|
183
|
+
*/
|
|
184
|
+
refresh(): Promise<boolean>;
|
|
185
|
+
/** Nested table/global operations run on this queue slot, not behind their own awaiting parent.
|
|
186
|
+
* @param operation Work executed under the domain queue and backend writer lock.
|
|
187
|
+
* @returns The operation result after the locked work settles.
|
|
188
|
+
*/
|
|
189
|
+
exclusive<T>(operation: () => Promise<T>): Promise<T>;
|
|
175
190
|
/**
|
|
176
191
|
* Close this domain: reject new writes immediately, drain already-queued
|
|
177
192
|
* writes (their events still emit), close the unit, then free the name via
|