@deepseek-ai/dsh-settings 0.1.6-alpha.2 → 0.1.7-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,10 +27,8 @@ export interface RedactedValue {
27
27
  }
28
28
  /**
29
29
  * Remove every `role('secret')` field a schema declares from a value. The
30
- * walker follows `object`, `dict`, and `array` containers; a secret must be
31
- * declared directly on a field reachable through those containers (a secret
32
- * buried inside a union branch or transform is not reachable and must not be
33
- * modeled that way). The input is never mutated.
30
+ * walker visits every union branch, conservatively removing any field declared
31
+ * secret by a branch. The input is never mutated.
34
32
  * @param schema - live schemastery schema describing the value.
35
33
  * @param value - the value to strip; `undefined` yields an empty record with
36
34
  * object-property secret slots still enumerated.
@@ -52,19 +52,19 @@ function walk(node, value, path, secrets) {
52
52
  return value;
53
53
  return value.map((entry, index) => walk(node.inner, entry, [...path, String(index)], secrets));
54
54
  }
55
+ case 'union':
56
+ case 'intersect':
57
+ return (node.list ?? []).reduce((current, child) => walk(child, current, path, secrets), value);
58
+ case 'transform':
59
+ return walk(node.inner, value, path, secrets);
55
60
  default:
56
- // TODO(settings-wire-redaction): Fail closed instead — a secret reachable
57
- // only through a union, intersection, or transform is returned verbatim
58
- // here, with nothing recording that it was missed.
59
61
  return value;
60
62
  }
61
63
  }
62
64
  /**
63
65
  * Remove every `role('secret')` field a schema declares from a value. The
64
- * walker follows `object`, `dict`, and `array` containers; a secret must be
65
- * declared directly on a field reachable through those containers (a secret
66
- * buried inside a union branch or transform is not reachable and must not be
67
- * modeled that way). The input is never mutated.
66
+ * walker visits every union branch, conservatively removing any field declared
67
+ * secret by a branch. The input is never mutated.
68
68
  * @param schema - live schemastery schema describing the value.
69
69
  * @param value - the value to strip; `undefined` yields an empty record with
70
70
  * object-property secret slots still enumerated.
@@ -73,6 +73,12 @@ function walk(node, value, path, secrets) {
73
73
  export function redactSecrets(schema, value) {
74
74
  const secrets = [];
75
75
  const stripped = walk(schema, value, [], secrets);
76
- return { value: stripped, secrets };
76
+ const positions = new Map();
77
+ for (const secret of secrets) {
78
+ const key = JSON.stringify(secret.path);
79
+ const previous = positions.get(key);
80
+ positions.set(key, { ...secret, set: secret.set || previous?.set === true });
81
+ }
82
+ return { value: stripped, secrets: [...positions.values()] };
77
83
  }
78
84
  //# sourceMappingURL=redact.js.map
@@ -0,0 +1,24 @@
1
+ import z from '@deepseek-ai/schemastery';
2
+ /** Remove runtime references from a configuration snapshot.
3
+ * @param value Parsed Config output.
4
+ * @returns Detached ordinary values suitable for redaction and forms.
5
+ */
6
+ export declare function plainConfig(value: unknown): unknown;
7
+ /** Select fields whose nearest volatile ancestor makes them editable without remounting.
8
+ * @param schema The plugin's Config schema.
9
+ * @returns A plain form schema, or undefined when no field is live.
10
+ */
11
+ export declare function volatileForm(schema: z): z | undefined;
12
+ /** Project only schema-declared fields, excluding ordinary configuration.
13
+ * @param schema The filtered form schema.
14
+ * @param value Plain raw or resolved config.
15
+ * @returns The fields visible to this form.
16
+ */
17
+ export declare function projectForm(schema: z, value: unknown): unknown;
18
+ /** Check that a field path lies beneath a declared volatile node.
19
+ * @param schema Complete plugin Config schema.
20
+ * @param path Field path addressed by a form edit.
21
+ * @returns Whether the path can be edited live.
22
+ */
23
+ export declare function isVolatilePath(schema: z, path: readonly string[]): boolean;
24
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,85 @@
1
+ /** Derive editable forms and plain values from plugin Config schemas. */
2
+ import { redactSecrets } from "./redact.js";
3
+ import z from '@deepseek-ai/schemastery';
4
+ import { isVolatile } from '@deepseek-ai/cosmokit';
5
+ /** Remove runtime references from a configuration snapshot.
6
+ * @param value Parsed Config output.
7
+ * @returns Detached ordinary values suitable for redaction and forms.
8
+ */
9
+ export function plainConfig(value) {
10
+ if (isVolatile(value))
11
+ return plainConfig(value.get());
12
+ if (Array.isArray(value))
13
+ return value.map(plainConfig);
14
+ if (value !== null && typeof value === 'object') {
15
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, plainConfig(child)]));
16
+ }
17
+ return value;
18
+ }
19
+ function plainSchema(schema) {
20
+ const result = new z(schema.toJSON());
21
+ const walk = (node) => {
22
+ delete node.meta.volatile;
23
+ if (node.meta.role === 'secret') {
24
+ delete node.meta.default;
25
+ delete node.meta.required;
26
+ }
27
+ else if (node.meta.default !== undefined)
28
+ node.meta.default = redactSecrets(node, node.meta.default).value;
29
+ for (const child of Object.values(node.dict ?? {}))
30
+ walk(child);
31
+ if (node.inner)
32
+ walk(node.inner);
33
+ for (const child of node.list ?? [])
34
+ walk(child);
35
+ };
36
+ walk(result);
37
+ return result;
38
+ }
39
+ /** Select fields whose nearest volatile ancestor makes them editable without remounting.
40
+ * @param schema The plugin's Config schema.
41
+ * @returns A plain form schema, or undefined when no field is live.
42
+ */
43
+ export function volatileForm(schema) {
44
+ if (schema.meta.volatile)
45
+ return plainSchema(schema);
46
+ if (schema.type === 'object') {
47
+ const dict = Object.fromEntries(Object.entries(schema.dict ?? {}).flatMap(([key, child]) => {
48
+ const field = volatileForm(child);
49
+ return field === undefined ? [] : [[key, field]];
50
+ }));
51
+ return Object.keys(dict).length === 0 ? undefined : z.object(dict);
52
+ }
53
+ return undefined;
54
+ }
55
+ /** Whether a raw config node is an unevaluated `!!js` expression, kept whole rather than projected field by field. */
56
+ function isExpression(value) {
57
+ return Object.keys(value).length === 1 && typeof Reflect.get(value, '__jsExpr') === 'string';
58
+ }
59
+ /** Project only schema-declared fields, excluding ordinary configuration.
60
+ * @param schema The filtered form schema.
61
+ * @param value Plain raw or resolved config.
62
+ * @returns The fields visible to this form.
63
+ */
64
+ export function projectForm(schema, value) {
65
+ if (schema.type === 'object' && value !== null && typeof value === 'object' && !isExpression(value)) {
66
+ return Object.fromEntries(Object.entries(schema.dict ?? {}).flatMap(([key, child]) => {
67
+ const field = Reflect.get(value, key);
68
+ return field === undefined ? [] : [[key, projectForm(child, field)]];
69
+ }));
70
+ }
71
+ return value;
72
+ }
73
+ /** Check that a field path lies beneath a declared volatile node.
74
+ * @param schema Complete plugin Config schema.
75
+ * @param path Field path addressed by a form edit.
76
+ * @returns Whether the path can be edited live.
77
+ */
78
+ export function isVolatilePath(schema, path) {
79
+ if (schema.meta.volatile)
80
+ return true;
81
+ const [key, ...rest] = path;
82
+ const child = key === undefined ? undefined : schema.dict?.[key];
83
+ return child !== undefined && isVolatilePath(child, rest);
84
+ }
85
+ //# sourceMappingURL=schema.js.map
@@ -1,18 +1,8 @@
1
- /**
2
- * Client-safe type surface of the user-settings seam: the namespace brand, the
3
- * commit-origin union, the redacted views a configuration surface reads over
4
- * the Remote wire, and the seam's Cordis event declarations. Types only — no
5
- * runtime code, and nothing here reaches a Host-only symbol, so a Client
6
- * compilation face reads exactly the signatures the Host emits.
7
- *
8
- * @module @deepseek-ai/dsh-settings/types
9
- */
1
+ /** Client-safe configuration form views and change notifications. */
10
2
  import type { Branded } from '@deepseek-ai/dsh-brand';
11
3
  import type { JsonValue } from '@deepseek-ai/dsh-util-values';
12
- /** Nominal id of one registered settings namespace. */
4
+ /** Nominal id of one profile plugin entry. */
13
5
  export type SettingsNamespace = Branded<'SettingsNamespace'>;
14
- /** Origin of one committed settings change. */
15
- export type SettingsUpdateSource = 'update' | 'provider';
16
6
  /** One schema-declared secret slot inside a redacted namespace value. */
17
7
  export interface SettingsSecretView {
18
8
  /** Path from the section root to the removed field. */
@@ -21,27 +11,29 @@ export interface SettingsSecretView {
21
11
  set: boolean;
22
12
  }
23
13
  /**
24
- * Wire view of one registered namespace, always read under `redactSecrets`. The
14
+ * Wire view of one profile plugin entry, always read under `redactSecrets`. The
25
15
  * JSON-valued fields are `JsonValue` rather than the descriptor's `unknown`
26
16
  * because the Remote boundary admits no unconstrained data.
27
17
  */
28
18
  export interface SettingsNamespaceView {
19
+ /** Generate a page if no custom page is registered for this instance. */
20
+ autoGenerate: boolean;
29
21
  /** Namespace key (`llm-deepseek`, `llm-pi-ai`, …). */
30
22
  ns: string;
31
23
  /** Serialized schemastery schema envelope (`schema.toJSON()`); rehydrate with `new Schema(json)`. */
32
24
  schema: JsonValue;
33
25
  /** Redacted resolved value (schema defaults → composition base → user layer). */
34
26
  value: JsonValue;
35
- /** Redacted composition base layer, when the registrant declared one. */
27
+ /** Redacted composition base layer, with defaults resolved. */
36
28
  base?: JsonValue;
37
29
  /** Redacted raw user section, when one exists; a field's presence here marks it user-overridden. */
38
30
  user?: JsonValue;
39
31
  /** When the owner applies changes. */
40
- applies: 'live' | 'restart';
32
+ applies: 'live';
41
33
  /** Every schema-declared secret slot with its configured state. */
42
34
  secrets: SettingsSecretView[];
43
35
  /**
44
- * Monotonic revision of the raw user section this view was read at. Send it
36
+ * Monotonic revision of the raw entry configuration this view was read at. Send it
45
37
  * back as `expectedRevision` on a write so a stale editor is refused rather
46
38
  * than silently overwriting a concurrent change.
47
39
  */
@@ -60,42 +52,22 @@ export type SettingsPathOpView = {
60
52
  op: 'unset';
61
53
  path: string[];
62
54
  };
63
- /** Every registered namespace with the deployment facts a configuration page renders around them. */
55
+ /** Every profile plugin entry with the deployment facts a configuration page renders around them. */
64
56
  export interface SettingsDescribeValue {
65
- /** Whether the provider accepts writes; `false` disables every write control. */
57
+ /** Whether the profile accepts writes; `false` disables every write control. */
66
58
  writable: boolean;
67
- /** Whether a file-backed provider owns a local document, without exposing its Host path. */
59
+ /** Whether the configuration editor owns a local document, without exposing its Host path. */
68
60
  hasDocument: boolean;
69
- /** One view per registered namespace. */
61
+ /** One view per profile plugin entry. */
70
62
  namespaces: SettingsNamespaceView[];
71
63
  }
72
64
  declare module '@deepseek-ai/cordis' {
73
65
  interface Events {
74
66
  /**
75
- * Committed change to one registered namespace's resolved value. Emitted
76
- * after the provider persisted (for `update`) or published (`provider`)
77
- * the change; never emitted when the resolved value is deep-equal.
78
- * Listener failures are contained and logged — a sync throw and an async
79
- * rejection alike — except `INVARIANT`-coded failures, which rethrow
80
- * after every listener ran; that rethrow reaches the emitter only from
81
- * synchronous listeners, so invariant checks on this event must not be
82
- * async functions.
83
- * @param ns - the namespace whose resolved value changed.
84
- * @param next - the new resolved value.
85
- * @param prev - the previous resolved value.
86
- * @param source - whether the change entered through `update()` or the provider.
87
- * @mode emit
88
- */
89
- 'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void;
90
- /**
91
- * One registered namespace's RAW user section changed, whether or not the
92
- * resolved value did. `settings/updated` is the consumer-facing event and
93
- * stays deep-equal-gated; this one exists for configuration surfaces,
94
- * which must learn that a field went from inherited to overridden (same
95
- * resolved value, different meaning) and that their held revision is
96
- * stale. Listener containment matches `settings/updated`.
97
- * @param ns - the namespace whose stored section changed.
98
- * @param revision - the namespace's new revision.
67
+ * One profile entry's form values, availability, or page policy changed.
68
+ * Form clients re-read its schema, resolved values, and revision.
69
+ * @param ns Profile entry id.
70
+ * @param revision The entry's new revision.
99
71
  * @mode emit
100
72
  */
101
73
  'settings/document-updated'(ns: SettingsNamespace, revision: number): void;
@@ -1,11 +1,3 @@
1
- /**
2
- * Client-safe type surface of the user-settings seam: the namespace brand, the
3
- * commit-origin union, the redacted views a configuration surface reads over
4
- * the Remote wire, and the seam's Cordis event declarations. Types only — no
5
- * runtime code, and nothing here reaches a Host-only symbol, so a Client
6
- * compilation face reads exactly the signatures the Host emits.
7
- *
8
- * @module @deepseek-ai/dsh-settings/types
9
- */
1
+ /** Client-safe configuration form views and change notifications. */
10
2
  export {};
11
3
  //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-settings",
3
3
  "description": "Abstract user-settings seam (ctx.settings) for the DeepSeek Harness",
4
- "version": "0.1.6-alpha.2",
4
+ "version": "0.1.7-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -18,10 +18,6 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
- "./invariant": {
22
- "types": "./lib/types/invariant.d.ts",
23
- "default": "./lib/invariant.js"
24
- },
25
21
  "./types": {
26
22
  "types": "./lib/types/types.d.ts",
27
23
  "default": "./lib/types/types.js"
@@ -31,26 +27,34 @@
31
27
  },
32
28
  "files": [
33
29
  "lib/index.js",
34
- "lib/invariant.js",
35
30
  "lib/types/**/*.js",
36
31
  "lib/types/**/*.d.ts"
37
32
  ],
38
33
  "license": "MIT",
39
34
  "peerDependencies": {
40
- "@deepseek-ai/cordis": "^4.0.2",
41
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
42
- "@deepseek-ai/dsh-invariants": "^0.1.6-alpha.2",
43
- "@deepseek-ai/schemastery": "^3.18.2",
44
- "@deepseek-ai/dsh-brand": "^0.1.6-alpha.2"
35
+ "@deepseek-ai/cordis": "~4.0.4",
36
+ "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
37
+ "@deepseek-ai/dsh-brand": "0.1.7-alpha.2",
38
+ "@deepseek-ai/schemastery": "~3.18.4",
39
+ "@deepseek-ai/dsh-invariants": "0.1.7-alpha.2"
45
40
  },
46
41
  "devDependencies": {
47
- "@deepseek-ai/cordis": "^4.0.2",
48
- "@deepseek-ai/dsh-brand": "^0.1.6-alpha.2",
49
- "@deepseek-ai/dsh-invariants": "^0.1.6-alpha.2",
50
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
51
- "@deepseek-ai/schemastery": "^3.18.2"
42
+ "yaml": "^2.8.1",
43
+ "@deepseek-ai/cordis": "~4.0.4",
44
+ "@deepseek-ai/dsh-brand": "0.1.7-alpha.2",
45
+ "@deepseek-ai/dsh-invariants": "0.1.7-alpha.2",
46
+ "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
47
+ "@deepseek-ai/schemastery": "~3.18.4",
48
+ "@deepseek-ai/cordis-plugin-timer": "~1.1.6",
49
+ "@deepseek-ai/dsh-app-boot": "0.1.7-alpha.2",
50
+ "@deepseek-ai/dsh-hmr": "0.1.7-alpha.2",
51
+ "@deepseek-ai/dsh-agent-default-model": "0.1.7-alpha.2"
52
52
  },
53
53
  "dependencies": {
54
- "@deepseek-ai/dsh-util-values": "^0.1.6-alpha.2"
54
+ "yaml": "^2.8.1",
55
+ "@deepseek-ai/dsh-util-values": "0.1.7-alpha.2",
56
+ "@deepseek-ai/dsh-config-editor": "0.1.7-alpha.2",
57
+ "@deepseek-ai/cordis-plugin-loader": "~1.0.5",
58
+ "@deepseek-ai/cosmokit": "~1.8.5"
55
59
  }
56
60
  }
package/lib/invariant.js DELETED
@@ -1,35 +0,0 @@
1
- import { deepEqualJson } from "@deepseek-ai/dsh-util-values";
2
- //#region lib/types/invariant.js
3
- /**
4
- * Package-owned invariant companion for `@deepseek-ai/dsh-settings`.
5
- * @module @deepseek-ai/dsh-settings/invariant
6
- */
7
- const PACKAGE_NAME = "@deepseek-ai/dsh-settings";
8
- /** Cordis companion plugin name. */
9
- const name = "settings-invariant";
10
- /** Service required before the companion can reserve package ownership. */
11
- const inject = ["invariants"];
12
- /**
13
- * Install the commit-event contract: `settings/updated` fires only for a
14
- * currently registered namespace, only when the resolved value changed, and
15
- * only with the service's authoritative resolved value — all judged with the
16
- * seam's own equality predicate.
17
- */
18
- const install = (ctx, fail) => {
19
- ctx.on("settings/updated", (ns, next, prev) => {
20
- const settings = ctx.get("settings");
21
- if (settings === void 0) fail(`settings/updated for "${ns}" emitted without a live settings service`);
22
- const current = settings.get(ns);
23
- if (current === void 0) fail(`settings/updated for "${ns}" emitted while the namespace is unregistered`);
24
- if (!deepEqualJson(current, next)) fail(`settings/updated for "${ns}" does not match the authoritative resolved value`);
25
- if (deepEqualJson(next, prev)) fail(`settings/updated for "${ns}" emitted without a resolved-value change`);
26
- });
27
- };
28
- /**
29
- * Register this package's invariant companion.
30
- * @param ctx - Cordis context carrying the invariant service.
31
- * @returns the installed registration's disposer after setup succeeds.
32
- */
33
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
34
- //#endregion
35
- export { apply, inject, name };
@@ -1,16 +0,0 @@
1
- /**
2
- * Package-owned invariant companion for `@deepseek-ai/dsh-settings`.
3
- * @module @deepseek-ai/dsh-settings/invariant
4
- */
5
- import type { Context } from '@deepseek-ai/cordis';
6
- /** Cordis companion plugin name. */
7
- export declare const name = "settings-invariant";
8
- /** Service required before the companion can reserve package ownership. */
9
- export declare const inject: string[];
10
- /**
11
- * Register this package's invariant companion.
12
- * @param ctx - Cordis context carrying the invariant service.
13
- * @returns the installed registration's disposer after setup succeeds.
14
- */
15
- export declare const apply: (ctx: Context) => Promise<() => void>;
16
- //# sourceMappingURL=invariant.d.ts.map
@@ -1,41 +0,0 @@
1
- /**
2
- * Package-owned invariant companion for `@deepseek-ai/dsh-settings`.
3
- * @module @deepseek-ai/dsh-settings/invariant
4
- */
5
- import { deepEqualJson } from '@deepseek-ai/dsh-util-values';
6
- const PACKAGE_NAME = '@deepseek-ai/dsh-settings';
7
- /** Cordis companion plugin name. */
8
- export const name = 'settings-invariant';
9
- /** Service required before the companion can reserve package ownership. */
10
- export const inject = ['invariants'];
11
- /**
12
- * Install the commit-event contract: `settings/updated` fires only for a
13
- * currently registered namespace, only when the resolved value changed, and
14
- * only with the service's authoritative resolved value — all judged with the
15
- * seam's own equality predicate.
16
- */
17
- const install = (ctx, fail) => {
18
- ctx.on('settings/updated', (ns, next, prev) => {
19
- const settings = ctx.get('settings');
20
- if (settings === undefined) {
21
- fail(`settings/updated for "${ns}" emitted without a live settings service`);
22
- }
23
- const current = settings.get(ns);
24
- if (current === undefined) {
25
- fail(`settings/updated for "${ns}" emitted while the namespace is unregistered`);
26
- }
27
- if (!deepEqualJson(current, next)) {
28
- fail(`settings/updated for "${ns}" does not match the authoritative resolved value`);
29
- }
30
- if (deepEqualJson(next, prev)) {
31
- fail(`settings/updated for "${ns}" emitted without a resolved-value change`);
32
- }
33
- });
34
- };
35
- /**
36
- * Register this package's invariant companion.
37
- * @param ctx - Cordis context carrying the invariant service.
38
- * @returns the installed registration's disposer after setup succeeds.
39
- */
40
- export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
41
- //# sourceMappingURL=invariant.js.map