dsh-perm-gate 4.2.0-beta.4 → 4.2.0-beta.5

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/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { Config, ensureDataDir, readRulesFromSettings, readVolatileValue, resolveVolatileConfig, resolveDshHome, resolveDataDir, resolveGatePresets, resolvePermissiveStrategies, resolveRulesFile } from './config.js';
4
+ import { appendAllowToSettings, readRulesFileDoc, replaceAllowInSettings } from './allowlist.js';
4
5
  import { runDryRun } from './dry-run.js';
5
6
  import { registerDryRunRoute, registerEventsRoute, registerHealthRoute, registerLearningRoute, registerNetworkRoute, registerReceiverRoute, registerReviewRoutes, registerRulesRoute } from './events.js';
6
7
  import { buildReceiverInfo } from './receiver-info.js';
@@ -176,10 +177,37 @@ export function apply(ctx, config = {}) {
176
177
  // own entry (0.1.7 has no second projectable namespace). Unconfigured
177
178
  // (bare defaults) -> undefined -> the rules file stays the source.
178
179
  const readRulesDocument = () => readRulesFromSettings({ get: () => readVolatileValue(config.rules) });
180
+ // ─── Host settings seam (allowlist write-back) ────────────────────────────
181
+ // The `settings` service mutates this entry's own config in the active
182
+ // profile patch (agent-default-model injects the same service). Its writes
183
+ // land in the `rules` volatile field, whose change watcher below reloads the
184
+ // runtime — so an "allow always" / allowlist edit takes effect on the next
185
+ // tool call even when the rules document (not the file) is the live source.
186
+ // The inject is a child effect: a host without the settings service leaves
187
+ // the ref unset and the runtime falls back to its rules-file write paths.
188
+ const ENTRY_ID = 'dsh-perm-gate';
189
+ const settingsRef = {};
190
+ ctx.inject(['settings'], (child) => {
191
+ settingsRef.svc = child.settings;
192
+ });
193
+ const settingsWrite = (write) => {
194
+ const svc = settingsRef.svc;
195
+ if (svc === undefined)
196
+ return false;
197
+ const scope = {
198
+ get: () => readVolatileValue(config.rules),
199
+ update: async (section) => { await svc.update(ENTRY_ID, { rules: section }); },
200
+ };
201
+ void write(scope, readRulesFileDoc(rulesFilePath)).catch((e) => {
202
+ console.warn('[dsh-perm-gate] settings rules write failed:', e);
203
+ });
204
+ return true;
205
+ };
179
206
  // Plugin-owned data files live under $DSH_HOME (node_modules may be
180
207
  // read-only). The default resolves to `$DSH_HOME` / `~/.dsh`, so a profile
181
208
  // entry that omits `config` still records events, snapshots and learning.
182
209
  const dataDir = resolveDataDir(typeof config.dshHome === 'string' ? config.dshHome : undefined);
210
+ const rulesFilePath = resolveRulesFile(typeof config.rulesFile === 'string' ? config.rulesFile : undefined, dataDir);
183
211
  // Host model-group services (typed minimally; supplied by the dsh runtime
184
212
  // through the loader `inject` — dsh-approval-gate demonstrates the same
185
213
  // contract). The llm service backs the `host` receiver; agentDefaultModel
@@ -321,9 +349,16 @@ export function apply(ctx, config = {}) {
321
349
  // The rules document lives in the plugin's data dir by default, so a
322
350
  // `$DSH_HOME/perm-gate/rules.yml` the user writes is actually loaded without
323
351
  // also having to declare `rulesFile` in the composition entry.
324
- rulesFile: resolveRulesFile(typeof config.rulesFile === 'string' ? config.rulesFile : undefined, dataDir),
352
+ rulesFile: rulesFilePath,
325
353
  // Settings-sourced rules take precedence over the file when present.
326
354
  readRulesDocument,
355
+ // Allowlist write-back through the settings service (this entry's own
356
+ // `rules` config field, seeded from the file document on first write);
357
+ // a falsy verdict (service not wired) falls back to the rules file.
358
+ allowlistWriter: {
359
+ append: (pattern, reason) => settingsWrite((scope, seed) => appendAllowToSettings(scope, pattern, reason, seed)),
360
+ replace: (patterns, reason) => settingsWrite((scope, seed) => replaceAllowInSettings(scope, patterns, reason, seed)),
361
+ },
327
362
  // The whole gate is scoped to the presets that opt into it (default: the
328
363
  // `permissive` tier this plugin adds); elsewhere it stands down entirely.
329
364
  gatePresets: resolveGatePresets(Array.isArray(config.gatePresets) ? config.gatePresets : undefined),
@@ -505,9 +540,8 @@ export function apply(ctx, config = {}) {
505
540
  });
506
541
  if (offDryRun !== undefined)
507
542
  ctx.effect(() => () => { offDryRun(); }, 'dsh-perm-gate: dry-run route');
508
- // Permissions view: prefer the settings namespace (dsh-perm-gate-rules)
509
- // when it carries a real configuration; fall back to the rules file.
510
- const rulesFilePath = resolveRulesFile(typeof config.rulesFile === 'string' ? config.rulesFile : undefined, dataDir);
543
+ // Permissions view: prefer the entry's `rules` config field when it
544
+ // carries a real configuration; fall back to the rules file.
511
545
  const offRules = registerRulesRoute(webServer, {
512
546
  view: () => {
513
547
  const settingsDoc = readRulesDocument();
@@ -37,7 +37,7 @@ export interface RulesView {
37
37
  */
38
38
  export declare function readRulesView(rulesFile: string): RulesView;
39
39
  /** The pseudo-path a settings view reports (there is no file to name). */
40
- export declare const SETTINGS_RULES_VIEW_PATH = "settings:dsh-perm-gate-rules";
40
+ export declare const SETTINGS_RULES_VIEW_PATH = "config:dsh-perm-gate#rules";
41
41
  /**
42
42
  * Render the settings-sourced rules for display — the read-only face of the
43
43
  * `dsh-perm-gate-rules` namespace, the twin of {@link readRulesView}.
package/lib/rules-view.js CHANGED
@@ -79,7 +79,7 @@ export function readRulesView(rulesFile) {
79
79
  }
80
80
  }
81
81
  /** The pseudo-path a settings view reports (there is no file to name). */
82
- export const SETTINGS_RULES_VIEW_PATH = 'settings:dsh-perm-gate-rules';
82
+ export const SETTINGS_RULES_VIEW_PATH = 'config:dsh-perm-gate#rules';
83
83
  /**
84
84
  * Render the settings-sourced rules for display — the read-only face of the
85
85
  * `dsh-perm-gate-rules` namespace, the twin of {@link readRulesView}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-perm-gate",
3
- "version": "4.2.0-beta.4",
3
+ "version": "4.2.0-beta.5",
4
4
  "publishConfig": {
5
5
  "tag": "dsh-0.1.7"
6
6
  },