dsh-perm-gate 5.3.4 → 5.4.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.
@@ -30,6 +30,14 @@
30
30
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
31
31
  import { parse, stringify } from 'yaml';
32
32
  import { parsePermissionsDocument } from './rule.js';
33
+ import { CRED_FAMILY_IDS } from './cred-families.js';
34
+ /**
35
+ * Shipped version of the preset table. Activation backfills (idempotent, revocation-
36
+ * preserving) whenever the stored `presetRulesVersion` lags this number — bump it
37
+ * whenever `PRESET_RULES_YAML` gains entries that existing installs must receive
38
+ * (v1 added the seven `cred.*` family rows).
39
+ */
40
+ export const PRESET_TABLE_VERSION = 1;
33
41
  /** Tag prefix carried by every preset entry's reason; locates an entry for revocation. */
34
42
  export const PRESET_RULE_MARKER = '[deny-rule:';
35
43
  /** Extract the `[deny-rule:<id>]` tag from one reason string (undefined when untagged). */
@@ -140,6 +148,9 @@ deny:
140
148
  - command: [find]
141
149
  argv: { pipeline: ["find *-exec rm*", "find *-delete*"] }
142
150
  reason: "[deny-rule:find.exec-rm-recursive-force] find -exec rm removes the whole tree per match. Dry-run plain find, delete explicit paths."
151
+ - command: [rsync]
152
+ argv: { pipeline: ["rsync * --delete*", "rsync --delete*"] }
153
+ reason: "[deny-rule:rsync.delete] rsync --delete erases destination files absent from the source. Dry-run with rsync -n --delete first."
143
154
  - command: [dd]
144
155
  argv: { pipeline: ["dd * of=/dev/sd*", "dd * of=/dev/nvme*", "dd * of=/dev/mmcblk*"] }
145
156
  reason: "[deny-rule:dd.device-write] dd writing to a block device destroys the disk."
@@ -232,6 +243,36 @@ ask:
232
243
  args: ["*/.ssh", "*/.ssh/*", "*/.aws", "*/.aws/*", "*/.gnupg", "*/.gnupg/*", "*/.config/gcloud/*", "*/.claude/*", "*/.codex/*"]
233
244
  reason: "[deny-rule:secret.read-home-credential-dirs] Browsing ssh/cloud/coding-CLI credential stores requires confirmation."
234
245
 
246
+ # ── Credential family ladder (read side) ──────────────────────────────────
247
+ # One row per family, restricted to the file-READ tools: decide() pre-queries the
248
+ # rule layer for READ calls, so these rows are what a read hits (shell reads stay
249
+ # under the secret.* rows above, which match first in this section). The settings
250
+ # face rewrites each row as the family's rung: deny - moved to the deny section;
251
+ # ask - stays here; readonly/readwrite - enabled:false (readwrite additionally
252
+ # lists the family in credWriteExemptFamilies for the engine's write clause).
253
+ # Custom credentialPaths have no family and are never write-exempt.
254
+ - tools: [read, read_image, grep, glob, ls, lsp]
255
+ args: ["*/.ssh", "*/.ssh/*", "*id_rsa", "*id_ed25519", "*id_ecdsa", "*id_dsa", "*authorized_keys"]
256
+ reason: "[deny-rule:cred.ssh] Reading SSH keys or authorized_keys needs confirmation (family rung: ask)."
257
+ - tools: [read, read_image, grep, glob, ls, lsp]
258
+ args: ["*/.aws", "*/.aws/*", "*/.config/gcloud", "*/.config/gcloud/*", "*/.azure", "*/.azure/*", "*credentials.yaml", "*credentials.json"]
259
+ reason: "[deny-rule:cred.cloud] Reading cloud-provider credential stores needs confirmation (family rung: ask)."
260
+ - tools: [read, read_image, grep, glob, ls, lsp]
261
+ args: ["*/.npmrc", "*.npmrc", "*/.pypirc", "*/.netrc", "*_netrc", "*/pip.conf"]
262
+ reason: "[deny-rule:cred.package] Reading package-registry auth files needs confirmation (family rung: ask)."
263
+ - tools: [read, read_image, grep, glob, ls, lsp]
264
+ args: ["*/.git-credentials", "*.git-credentials"]
265
+ reason: "[deny-rule:cred.gitcreds] Reading the git credential store needs confirmation (family rung: ask)."
266
+ - tools: [read, read_image, grep, glob, ls, lsp]
267
+ args: ["*/.env", "*/.env.*", "*.env"]
268
+ reason: "[deny-rule:cred.env] Reading project env files needs confirmation (family rung: ask; readwrite also lets edits, npm-login-style)."
269
+ - tools: [read, read_image, grep, glob, ls, lsp]
270
+ args: ["*/.claude", "*/.claude/*", "*/.codex", "*/.codex/*", "*/.config/gh", "*/.config/gh/*"]
271
+ reason: "[deny-rule:cred.cli] Reading coding-CLI credential stores needs confirmation (family rung: ask)."
272
+ - tools: [read, read_image, grep, glob, ls, lsp]
273
+ args: ["*/.pgpass", "*pgpass", "*/.my.cnf"]
274
+ reason: "[deny-rule:cred.db] Reading database credential files needs confirmation (family rung: ask)."
275
+
235
276
  # Self-protection: editing the gate's own rule sources needs confirmation —
236
277
  # revocation must be a human act, not something an agent can slide through.
237
278
  # (Settings-namespace edits go through the host UI, not a tool call, and are
@@ -386,3 +427,99 @@ export function ensurePresetRulesFile(rulesFile, markerFile) {
386
427
  return { action: 'skipped', added: 0 };
387
428
  }
388
429
  }
430
+ /**
431
+ * The current rung per family, read out of a RAW rules document plus the write-exemption
432
+ * list (GET side of the settings face; the card renders straight from this map).
433
+ * A missing row reports 'ask' — the shipped default every fresh seed writes.
434
+ */
435
+ export function credRungsOf(rawDoc, exempt = []) {
436
+ const root = rawDoc !== null && typeof rawDoc === 'object' && !Array.isArray(rawDoc)
437
+ ? rawDoc
438
+ : {};
439
+ const target = root['permissions'] !== null && typeof root['permissions'] === 'object' && !Array.isArray(root['permissions'])
440
+ ? root['permissions']
441
+ : root;
442
+ const out = {};
443
+ for (const family of CRED_FAMILY_IDS) {
444
+ const tag = `cred.${family}`;
445
+ let rung = 'ask';
446
+ for (const key of ['deny', 'ask']) {
447
+ const arr = Array.isArray(target[key]) ? target[key] : [];
448
+ const row = arr.find((e) => presetRuleId(e.reason) === tag);
449
+ if (row !== undefined) {
450
+ if (row.enabled === false)
451
+ rung = exempt.includes(family) ? 'readwrite' : 'readonly';
452
+ else
453
+ rung = key === 'deny' ? 'deny' : 'ask';
454
+ break;
455
+ }
456
+ }
457
+ out[family] = rung;
458
+ }
459
+ return out;
460
+ }
461
+ /**
462
+ * Rewrite one `cred.<family>` row of a RAW rules document to the chosen rung, and
463
+ * maintain the paired write-exemption list. Both stores are returned together; the
464
+ * caller persists them in one settings update. Idempotent: applying the same rung
465
+ * twice is a no-op. Revocation-preserving elsewhere untouched — this function only
466
+ * ever moves or enables/disables the family's own row.
467
+ *
468
+ * `readonly` and `readwrite` both disable the read row (reads then fall through to
469
+ * the READ auto-allow); they differ only in `exempt`: readwrite additionally lists
470
+ * the family, which steps the engine's credential-store WRITE clause aside for that
471
+ * family's built-in paths — and nothing else.
472
+ */
473
+ export function setCredFamilyAction(rawDoc, exempt, family, rung) {
474
+ const root = rawDoc !== null && typeof rawDoc === 'object' && !Array.isArray(rawDoc)
475
+ ? { ...rawDoc }
476
+ : {};
477
+ const hasWrapper = root['permissions'] !== null && typeof root['permissions'] === 'object' && !Array.isArray(root['permissions']);
478
+ // Always work on a fresh wrapper copy — the shared `permissions` object is frozen
479
+ // on the live host, and `defaultAction` writes below must not hit it.
480
+ const target = hasWrapper ? { ...root['permissions'] } : root;
481
+ if (hasWrapper)
482
+ root['permissions'] = target;
483
+ const tag = `cred.${family}`;
484
+ // Settings snapshots are deep-frozen on the live host: containers AND elements.
485
+ // Every section array we touch is replaced by a copy first — `splice` on a frozen
486
+ // array throws in strict mode (`Cannot delete property`), the same class of failure
487
+ // as the frozen-row write. Copy-on-write for arrays and rows alike.
488
+ const takeSection = (key) => {
489
+ const cur = Array.isArray(target[key]) ? target[key] : [];
490
+ const copy = [...cur];
491
+ target[key] = copy;
492
+ return copy;
493
+ };
494
+ // Detach this family's row from wherever it sits. NO whole-table merge here: the
495
+ // merge dedupes per section, so a row previously moved deny↔ask would be
496
+ // re-appended to its preset section and the rung would silently revert (measured
497
+ // on the frozen-snapshot regression). Only this family's row is touched.
498
+ let entry;
499
+ for (const key of ['deny', 'ask']) {
500
+ const arr = takeSection(key);
501
+ const i = arr.findIndex((e) => presetRuleId(e.reason) === tag);
502
+ if (i >= 0) {
503
+ // Copy before mutate: settings snapshots are deep-frozen on the live host.
504
+ entry = { ...arr.splice(i, 1)[0] };
505
+ break;
506
+ }
507
+ }
508
+ if (entry === undefined) {
509
+ // Row absent (fresh doc or pruned): seed just this family's preset row.
510
+ const src = parsePresetDoc().ask.find((e) => presetRuleId(e.reason) === tag);
511
+ if (src !== undefined)
512
+ entry = rawEntry(src);
513
+ }
514
+ if (entry !== undefined) {
515
+ entry.enabled = rung !== 'readonly' && rung !== 'readwrite';
516
+ const destKey = rung === 'deny' ? 'deny' : 'ask';
517
+ const dest = takeSection(destKey);
518
+ dest.push(entry);
519
+ }
520
+ if (typeof root.defaultAction !== 'string' && typeof target.defaultAction !== 'string') {
521
+ target.defaultAction = 'ask';
522
+ }
523
+ const nextExempt = rung === 'readwrite' ? [...new Set([...exempt, family])] : exempt.filter((f) => f !== family);
524
+ return { doc: root, exempt: nextExempt };
525
+ }
package/lib/runtime.d.ts CHANGED
@@ -156,6 +156,21 @@ export interface PermGateRuntimeOptions extends PermGateConfig {
156
156
  */
157
157
  readonly readSafenetPass?: () => boolean;
158
158
  readonly readSafenetAsk?: () => boolean;
159
+ /**
160
+ * Live read of the `allowProtectedBranchPush` opt-out (volatile setting, default false
161
+ * keeps the P0 protected-branch floor). When true the floor steps aside AND the three
162
+ * flat push keywords in the deny-keyword blacklist are exempted — the keyword layer runs
163
+ * before every ask, so leaving them armed would make the switch a silent no-op. Nothing
164
+ * else widens: wipes, redirects, credentials and the rest of the floor stay armed, and
165
+ * the seeded `git.push-force` rule still asks a person.
166
+ */
167
+ readonly readAllowProtectedBranchPush?: () => boolean;
168
+ /**
169
+ * Live read of `credWriteExemptFamilies` — the readwrite rung of the credential-family
170
+ * ladder. Consulted ONLY by the engine's credential-store write clause; already filtered
171
+ * to the family roster by the wiring.
172
+ */
173
+ readonly readCredWriteExemptFamilies?: () => readonly string[];
159
174
  /**
160
175
  * Optional live source of the script-review switch: attach the body of every script a
161
176
  * call runs to the review, so the grader and the human judge code, not a filename.
@@ -529,6 +544,13 @@ export declare class PermGateRuntime {
529
544
  * An empty override is treated as "no meaningful override" (the blacklist is
530
545
  * protective and stays on); schemastery materializes unset arrays as `[]`. */
531
546
  private liveDenyKeywords;
547
+ /**
548
+ * The push-floor opt-out, read live so the settings toggle lands on the next call.
549
+ * `PUSH_FLOOR_KEYWORDS` are the flat blacklist entries that encode the same intent as
550
+ * the P0 protected-branch floor; the opt-out exempts exactly these, nothing more.
551
+ */
552
+ private liveAllowProtectedBranchPush;
553
+ private effectiveDenyKeywords;
532
554
  /** The user-declared credential paths, as configured (may be empty). */
533
555
  private liveCredentialPaths;
534
556
  /**
package/lib/runtime.js CHANGED
@@ -36,6 +36,12 @@ import { DEFAULT_DENY_KEYWORDS } from './deny-defaults.js';
36
36
  import { permissionPresetOf, presetInScope } from './preset.js';
37
37
  import { resolveTierMode } from './unattended.js';
38
38
  import { resolveGatePresets } from './config.js';
39
+ /**
40
+ * The flat blacklist entries that encode the same intent as the P0 protected-branch push
41
+ * floor. `allowProtectedBranchPush` exempts exactly these (and the floor itself) — every
42
+ * other keyword and floor branch stays armed.
43
+ */
44
+ const PUSH_FLOOR_KEYWORDS = new Set(['push --force', 'force-push', 'force push']);
39
45
  /** Locate a rule by its chain-wide index (deny entries first, then allow, then ask). */
40
46
  function findRuleByIndex(ruleset, index) {
41
47
  return (ruleset.deny.find((entry) => entry.index === index) ??
@@ -410,6 +416,8 @@ export class PermGateRuntime {
410
416
  caseInsensitive: this.options.caseInsensitivePaths ?? true,
411
417
  dshHome: this.options.dshHome,
412
418
  credentialPaths: this.liveCredentialPaths(),
419
+ allowProtectedBranchPush: this.liveAllowProtectedBranchPush(),
420
+ credWriteExempt: this.options.readCredWriteExemptFamilies?.() ?? [],
413
421
  scanKeys: this.liveScanKeys(),
414
422
  agentCandidates: extractAgentCandidates(exec),
415
423
  };
@@ -817,6 +825,20 @@ export class PermGateRuntime {
817
825
  const appended = extra.map((k) => String(k).trim()).filter((k) => k !== '');
818
826
  return appended.length === 0 ? base : [...new Set([...base, ...appended])];
819
827
  }
828
+ /**
829
+ * The push-floor opt-out, read live so the settings toggle lands on the next call.
830
+ * `PUSH_FLOOR_KEYWORDS` are the flat blacklist entries that encode the same intent as
831
+ * the P0 protected-branch floor; the opt-out exempts exactly these, nothing more.
832
+ */
833
+ liveAllowProtectedBranchPush() {
834
+ return this.options.readAllowProtectedBranchPush?.() ?? this.options.allowProtectedBranchPush ?? false;
835
+ }
836
+ effectiveDenyKeywords() {
837
+ const keywords = this.liveDenyKeywords();
838
+ if (!this.liveAllowProtectedBranchPush())
839
+ return keywords;
840
+ return keywords.filter((k) => !PUSH_FLOOR_KEYWORDS.has(String(k).trim()));
841
+ }
820
842
  /** The user-declared credential paths, as configured (may be empty). */
821
843
  liveCredentialPaths() {
822
844
  const read = this.options.readCredentialPaths;
@@ -855,7 +877,7 @@ export class PermGateRuntime {
855
877
  * call, the reason, or the event: the reason names the keyword, never the blob.
856
878
  */
857
879
  denyKeywordHit(exec) {
858
- const keywords = this.liveDenyKeywords();
880
+ const keywords = this.effectiveDenyKeywords();
859
881
  if (keywords.length === 0)
860
882
  return undefined;
861
883
  const text = keywordScanText(exec.name, exec.arguments ?? {}, exec.commandText, this.liveScanKeys());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-perm-gate",
3
- "version": "5.3.4",
3
+ "version": "5.4.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "tag": "latest"
@@ -69,15 +69,15 @@
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=24",
72
- "dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4"
72
+ "dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "@deepseek-ai/cordis": "^4.0.1",
76
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4",
77
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4",
78
- "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4",
79
- "@deepseek-ai/dsh-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4",
80
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4"
76
+ "@deepseek-ai/dsh-client-locale": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5",
77
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5",
78
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5",
79
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5",
80
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5"
81
81
  },
82
82
  "dependencies": {
83
83
  "@deepseek-ai/schemastery": "^3.18.4",
@@ -86,10 +86,10 @@
86
86
  },
87
87
  "devDependencies": {
88
88
  "@deepseek-ai/cordis": "^4.0.1",
89
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.4",
90
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.4",
91
- "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.4",
92
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.4",
89
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
90
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
91
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.5",
92
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.5",
93
93
  "@eslint/js": "^10.0.1",
94
94
  "@types/node": "^22.0.0",
95
95
  "@types/react": "~18.3.1",