@yadsh/dsh-prompt-firewall 0.1.2 → 0.1.3

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/client.js CHANGED
@@ -4,8 +4,8 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react = require("react");
8
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react = require("react");
9
9
  //#region ../../node_modules/.pnpm/zod@4.5.2/node_modules/zod/v4/core/util.js
10
10
  function getEnumValues(entries) {
11
11
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
@@ -5179,20 +5179,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5179
5179
  }]
5180
5180
  };
5181
5181
  //#endregion
5182
- //#region src/client/settings-store.ts
5182
+ //#region ../../packages/plugin-kit/lib/client/plugin-card-css.js
5183
5183
  /**
5184
- * React invokes external-store callbacks as plain functions. SettingsScope's
5185
- * methods depend on their receiver, so forward calls through stable wrappers.
5184
+ * Canonical DSH settings-plugin card shell CSS — the single source of truth
5185
+ * for the shared `dsh-plugin-card` outer shell (see AGENTS.md). Plugins must
5186
+ * inject this CSS unchanged and may only append rules for their own controls
5187
+ * inside the card body; plugin-specific shell styling is forbidden.
5186
5188
  */
5187
- function bindSettingsExternalStore(scope) {
5188
- return {
5189
- subscribe: (listener) => scope.subscribe(listener),
5190
- getSnapshot: () => scope.getSnapshot()
5191
- };
5192
- }
5193
- //#endregion
5194
- //#region src/client/styles.ts
5195
- const styles = `
5189
+ const PLUGIN_CARD_SHELL_CSS = `
5196
5190
  .dsh-plugin-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
5197
5191
  .dsh-plugin-card:hover{border-color:var(--dsw-alias-label-dimmed)}
5198
5192
  .dsh-plugin-card--open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
@@ -5205,6 +5199,153 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5205
5199
  .dsh-plugin-card__chevron{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}
5206
5200
  .dsh-plugin-card--open .dsh-plugin-card__chevron{transform:rotate(180deg)}
5207
5201
  .dsh-plugin-card__body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
5202
+ `;
5203
+ //#endregion
5204
+ //#region ../../packages/plugin-kit/lib/client/chevron.js
5205
+ /**
5206
+ * The standard disclosure chevron of the shared settings-card shell: a
5207
+ * 14x14 inline SVG stroked with `currentColor` (round caps and joins).
5208
+ * Font glyphs must not be used for this shape — their appearance and
5209
+ * baseline vary by font and encoding.
5210
+ */
5211
+ function ChevronDown() {
5212
+ return (0, react_jsx_runtime.jsx)("svg", {
5213
+ className: "dsh-plugin-card__chevron",
5214
+ viewBox: "0 0 14 14",
5215
+ fill: "none",
5216
+ "aria-hidden": "true",
5217
+ children: (0, react_jsx_runtime.jsx)("path", {
5218
+ d: "m3.5 5.25 3.5 3.5 3.5-3.5",
5219
+ stroke: "currentColor",
5220
+ strokeLinecap: "round",
5221
+ strokeLinejoin: "round"
5222
+ })
5223
+ });
5224
+ }
5225
+ //#endregion
5226
+ //#region ../../packages/plugin-kit/lib/client/card-shell.js
5227
+ /**
5228
+ * Shared outer shell of a plugin configuration card (AGENTS.md contract): a
5229
+ * direct `<li>` child of the host list with a full-width header button
5230
+ * (`type="button"`, `aria-expanded`, accessible show/hide label, the
5231
+ * title/description stack, an optional status badge, then the chevron) and a
5232
+ * body rendered only while open. Plugin-specific controls belong inside the
5233
+ * body and must not restyle the shell.
5234
+ */
5235
+ function CardShell(props) {
5236
+ const [open, setOpen] = (0, react.useState)(false);
5237
+ const body = props.bodyClassName === void 0 ? "dsh-plugin-card__body" : `dsh-plugin-card__body ${props.bodyClassName}`;
5238
+ return (0, react_jsx_runtime.jsxs)("li", {
5239
+ className: open ? "dsh-plugin-card dsh-plugin-card--open" : "dsh-plugin-card",
5240
+ children: [(0, react_jsx_runtime.jsxs)("button", {
5241
+ type: "button",
5242
+ className: "dsh-plugin-card__header",
5243
+ "aria-expanded": open,
5244
+ "aria-label": props.label(open),
5245
+ onClick: () => {
5246
+ setOpen(!open);
5247
+ },
5248
+ children: [
5249
+ (0, react_jsx_runtime.jsxs)("span", {
5250
+ className: "dsh-plugin-card__head-text",
5251
+ children: [(0, react_jsx_runtime.jsx)("span", {
5252
+ className: "dsh-plugin-card__name",
5253
+ children: props.title
5254
+ }), (0, react_jsx_runtime.jsx)("span", {
5255
+ className: "dsh-plugin-card__description",
5256
+ children: props.description
5257
+ })]
5258
+ }),
5259
+ props.badge ?? null,
5260
+ (0, react_jsx_runtime.jsx)(ChevronDown, {})
5261
+ ]
5262
+ }), open ? (0, react_jsx_runtime.jsx)("div", {
5263
+ className: body,
5264
+ children: props.children
5265
+ }) : null]
5266
+ });
5267
+ }
5268
+ function noop() {}
5269
+ /**
5270
+ * Inject one stylesheet tag for the plugin's card CSS. The tag carries
5271
+ * `data-plugin="<pluginName>"` and is guarded against double injection; the
5272
+ * returned disposer removes the tag when this call created it. No-op in
5273
+ * non-DOM environments (headless bundles, module probes).
5274
+ */
5275
+ function injectCardStyles(pluginName, css) {
5276
+ if (typeof document === "undefined") return noop;
5277
+ const selector = `style[data-plugin="${pluginName}"]`;
5278
+ if (typeof document.querySelector === "function" && document.querySelector(selector) !== null) return noop;
5279
+ const tag = document.createElement("style");
5280
+ tag.dataset.plugin = pluginName;
5281
+ tag.textContent = css;
5282
+ document.head.appendChild(tag);
5283
+ return () => {
5284
+ tag.remove();
5285
+ };
5286
+ }
5287
+ /**
5288
+ * Register the card component into the shared settings-plugins slot. Returns
5289
+ * the registration disposer. Use this variant when the plugin drives the
5290
+ * slot's inject-factory itself (e.g. to dispose a form controller alongside).
5291
+ */
5292
+ function registerSettingsSlot(host, options) {
5293
+ const slotOptions = {
5294
+ name: options.slotName ?? "settings.plugin.item",
5295
+ key: options.key,
5296
+ ...options.locale === void 0 ? {} : { locale: options.locale },
5297
+ ...options.inject === void 0 ? {} : { inject: options.inject }
5298
+ };
5299
+ return host.slots.register(slotOptions, options.component);
5300
+ }
5301
+ /**
5302
+ * Full bootstrap: inject {@link options.styles} once (when provided), then
5303
+ * mount the card into the settings-plugins slot. Returns a disposer removing
5304
+ * the slot effect and the injected stylesheet.
5305
+ */
5306
+ function registerSettingsCard(host, options) {
5307
+ const removeStyles = options.styles === void 0 || options.pluginName === void 0 ? void 0 : injectCardStyles(options.pluginName, options.styles);
5308
+ const slotName = options.slotName ?? "settings.plugin.item";
5309
+ const disposeEffect = host.slots.inject(slotName, () => registerSettingsSlot(host, options));
5310
+ return () => {
5311
+ disposeEffect();
5312
+ removeStyles?.();
5313
+ };
5314
+ }
5315
+ //#endregion
5316
+ //#region ../../packages/plugin-kit/lib/client/settings-store.js
5317
+ /**
5318
+ * React calls external-store callbacks as plain functions, while
5319
+ * SettingsScope's methods depend on their receiver. Forwarding the methods
5320
+ * directly loses `this` and crashes while reading the internal store, so
5321
+ * bind them through stable wrappers.
5322
+ */
5323
+ function bindSettingsExternalStore(scope) {
5324
+ return {
5325
+ subscribe: (listener) => scope.subscribe(listener),
5326
+ getSnapshot: () => scope.getSnapshot()
5327
+ };
5328
+ }
5329
+ //#endregion
5330
+ //#region ../../packages/plugin-kit/lib/client/polling.js
5331
+ function startVisibilityAwarePolling(refresh, intervalMs, visibilityDocument = document, pollingWindow = window) {
5332
+ const refreshWhenVisible = () => {
5333
+ if (!visibilityDocument.hidden) refresh();
5334
+ };
5335
+ const handleVisibilityChange = () => {
5336
+ refreshWhenVisible();
5337
+ };
5338
+ refreshWhenVisible();
5339
+ const timer = pollingWindow.setInterval(refreshWhenVisible, intervalMs);
5340
+ visibilityDocument.addEventListener("visibilitychange", handleVisibilityChange);
5341
+ return () => {
5342
+ pollingWindow.clearInterval(timer);
5343
+ visibilityDocument.removeEventListener("visibilitychange", handleVisibilityChange);
5344
+ };
5345
+ }
5346
+ //#endregion
5347
+ //#region src/client/styles.ts
5348
+ const styles = `${PLUGIN_CARD_SHELL_CSS}
5208
5349
  .pf-body,.pf-body *{box-sizing:border-box}
5209
5350
  .pf-body{padding-top:16px;display:grid;gap:18px;color:var(--dsw-alias-label-primary)}
5210
5351
  .pf-section{display:grid;gap:12px}.pf-section-title{display:flex;justify-content:space-between;align-items:center;gap:12px}.pf-section-title h3{font-size:13px;margin:0}.pf-muted{color:var(--dsw-alias-label-tertiary);font-size:12px;margin:0;line-height:1.5}
@@ -5221,32 +5362,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5221
5362
  @media(max-width:720px){.pf-grid{grid-template-columns:1fr}.pf-stats{grid-template-columns:repeat(2,minmax(0,1fr))}.pf-editor{grid-template-columns:1fr 1fr}.pf-editor input{grid-column:1/-1}.pf-rule{grid-template-columns:minmax(0,1fr) 58px auto}.pf-rule .pf-kind{display:none}}
5222
5363
  `;
5223
5364
  //#endregion
5224
- //#region src/client/index.tsx
5225
- const SETTINGS_NAMESPACE = "prompt-firewall";
5226
- const REFRESH_INTERVAL_MS = 3e3;
5365
+ //#region src/client/sections.tsx
5227
5366
  const EMPTY_LIST = Object.freeze([]);
5228
5367
  function list(config, field) {
5229
5368
  return config?.[field] ?? EMPTY_LIST;
5230
5369
  }
5231
- function displayError(error) {
5232
- if (error instanceof Error) return error.message;
5233
- if (typeof error === "string") return error;
5234
- return "Could not load Prompt Inspector data.";
5235
- }
5236
- function ChevronDown() {
5237
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
5238
- className: "dsh-plugin-card__chevron",
5239
- viewBox: "0 0 14 14",
5240
- fill: "none",
5241
- "aria-hidden": "true",
5242
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
5243
- d: "m3.5 5.25 3.5 3.5 3.5-3.5",
5244
- stroke: "currentColor",
5245
- strokeLinecap: "round",
5246
- strokeLinejoin: "round"
5247
- })
5248
- });
5249
- }
5250
5370
  function Toggle(props) {
5251
5371
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5252
5372
  className: "pf-toggle-row",
@@ -5264,68 +5384,163 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5264
5384
  })]
5265
5385
  });
5266
5386
  }
5267
- function PromptFirewallCard({ scope, inspect, setSectionPolicy }) {
5268
- const settingsStore = (0, react.useMemo)(() => bindSettingsExternalStore(scope), [scope]);
5269
- const settings = (0, react.useSyncExternalStore)(settingsStore.subscribe, settingsStore.getSnapshot, settingsStore.getSnapshot);
5270
- const config = settings.value;
5271
- const writable = settings.status === "ready" && settings.writable;
5272
- const [open, setOpen] = (0, react.useState)(false);
5273
- const [inspector, setInspector] = (0, react.useState)(null);
5274
- const [error, setError] = (0, react.useState)(null);
5275
- const [refreshing, setRefreshing] = (0, react.useState)(false);
5387
+ function PolicySection(props) {
5388
+ const { config, writable, setPath } = props;
5389
+ const configuredMode = config?.mode ?? (config?.preset === "strict" ? "allowlist" : config?.preset === "audit-only" ? "audit" : "blocklist");
5390
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5391
+ className: "pf-section",
5392
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5393
+ className: "pf-section-title",
5394
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Policy" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5395
+ className: "pf-muted",
5396
+ children: "Changes apply live"
5397
+ })]
5398
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5399
+ className: "pf-grid",
5400
+ children: [
5401
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5402
+ checked: config?.enabled ?? true,
5403
+ disabled: !writable,
5404
+ label: "Firewall enabled",
5405
+ hint: "Keep auditing available when policy is off.",
5406
+ onChange: (value) => {
5407
+ setPath(["enabled"], value);
5408
+ }
5409
+ }),
5410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5411
+ checked: config?.protectCoreSections ?? true,
5412
+ disabled: !writable,
5413
+ label: "Protect core sections",
5414
+ hint: "Prevents ordinary rules from removing Harness internals.",
5415
+ onChange: (value) => {
5416
+ setPath(["protectCoreSections"], value);
5417
+ }
5418
+ }),
5419
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5420
+ className: "pf-field",
5421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Mode" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5422
+ className: "pf-control",
5423
+ value: configuredMode,
5424
+ disabled: !writable,
5425
+ onChange: (event) => {
5426
+ setPath(["mode"], event.currentTarget.value);
5427
+ },
5428
+ children: [
5429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5430
+ value: "off",
5431
+ children: "Off"
5432
+ }),
5433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5434
+ value: "audit",
5435
+ children: "Audit only"
5436
+ }),
5437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5438
+ value: "blocklist",
5439
+ children: "Blocklist"
5440
+ }),
5441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5442
+ value: "allowlist",
5443
+ children: "Allowlist"
5444
+ })
5445
+ ]
5446
+ })]
5447
+ }),
5448
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5449
+ className: "pf-field",
5450
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Preset" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5451
+ className: "pf-control",
5452
+ value: config?.preset ?? "",
5453
+ disabled: !writable,
5454
+ onChange: (event) => {
5455
+ const value = event.currentTarget.value;
5456
+ if (value === "") props.unsetPreset();
5457
+ else setPath(["preset"], value);
5458
+ },
5459
+ children: [
5460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5461
+ value: "",
5462
+ children: "None"
5463
+ }),
5464
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5465
+ value: "clean",
5466
+ children: "Clean"
5467
+ }),
5468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5469
+ value: "strict",
5470
+ children: "Strict"
5471
+ }),
5472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5473
+ value: "audit-only",
5474
+ children: "Audit only"
5475
+ })
5476
+ ]
5477
+ })]
5478
+ }),
5479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5480
+ className: "pf-field",
5481
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Unknown plugin sections" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5482
+ className: "pf-control",
5483
+ value: config?.unknownPluginPolicy ?? "allow",
5484
+ disabled: !writable,
5485
+ onChange: (event) => {
5486
+ setPath(["unknownPluginPolicy"], event.currentTarget.value);
5487
+ },
5488
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5489
+ value: "allow",
5490
+ children: "Allow"
5491
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5492
+ value: "block",
5493
+ children: "Block"
5494
+ })]
5495
+ })]
5496
+ })
5497
+ ]
5498
+ })]
5499
+ });
5500
+ }
5501
+ function LastRequestSection(props) {
5502
+ const last = props.inspector?.last ?? null;
5503
+ const effectiveMode = props.inspector?.config.mode ?? props.config?.mode ?? "blocklist";
5504
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5505
+ className: "pf-section",
5506
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5507
+ className: "pf-section-title",
5508
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Last request" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5509
+ className: "pf-btn",
5510
+ disabled: props.refreshing,
5511
+ onClick: () => {
5512
+ props.onRefresh();
5513
+ },
5514
+ children: props.refreshing ? "Refreshing…" : "Refresh"
5515
+ })]
5516
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5517
+ className: "pf-stats",
5518
+ children: [
5519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5520
+ className: "pf-stat",
5521
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: last?.totalSections ?? "—" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "sections" })]
5522
+ }),
5523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5524
+ className: "pf-stat",
5525
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: last?.blockedSections ?? "—" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "blocked" })]
5526
+ }),
5527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5528
+ className: "pf-stat",
5529
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: last === null ? "—" : "~" + String(last.estimatedTokensRemoved) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "tokens removed (estimate)" })]
5530
+ }),
5531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5532
+ className: "pf-stat",
5533
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: effectiveMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "effective mode" })]
5534
+ })
5535
+ ]
5536
+ })]
5537
+ });
5538
+ }
5539
+ function RulesSection(props) {
5540
+ const { config, writable, setPath } = props;
5276
5541
  const [rule, setRule] = (0, react.useState)("");
5277
5542
  const [ruleKind, setRuleKind] = (0, react.useState)("exact");
5278
5543
  const [ruleAction, setRuleAction] = (0, react.useState)("block");
5279
- const activeRequest = (0, react.useRef)(0);
5280
- const refresh = (0, react.useCallback)(async () => {
5281
- const request = ++activeRequest.current;
5282
- setRefreshing(true);
5283
- try {
5284
- const result = await inspect();
5285
- if (request !== activeRequest.current) return;
5286
- if (result.ok) {
5287
- setInspector(result.value);
5288
- setError(null);
5289
- } else setError(displayError(result.error));
5290
- } catch (cause) {
5291
- if (request === activeRequest.current) setError(displayError(cause));
5292
- } finally {
5293
- if (request === activeRequest.current) setRefreshing(false);
5294
- }
5295
- }, [inspect]);
5296
- (0, react.useEffect)(() => {
5297
- refresh();
5298
- const interval = window.setInterval(() => {
5299
- refresh();
5300
- }, REFRESH_INTERVAL_MS);
5301
- return () => {
5302
- window.clearInterval(interval);
5303
- activeRequest.current += 1;
5304
- };
5305
- }, [refresh]);
5306
- const setPath = (0, react.useCallback)((path, value) => {
5307
- const [field, nested] = path;
5308
- if (field === void 0) return;
5309
- if (nested === void 0) {
5310
- scope.set(field, value);
5311
- return;
5312
- }
5313
- const current = scope.getSnapshot().value;
5314
- const parent = field === "audit" ? current?.audit : current?.metrics;
5315
- scope.set(field, {
5316
- ...parent,
5317
- [nested]: value
5318
- });
5319
- }, [scope]);
5320
- const setPolicy = (0, react.useCallback)(async (name, policy) => {
5321
- const result = await setSectionPolicy(name, policy, scope.getSnapshot().revision);
5322
- if (!result.ok) setError(displayError(result.error));
5323
- await refresh();
5324
- }, [
5325
- refresh,
5326
- scope,
5327
- setSectionPolicy
5328
- ]);
5329
5544
  const addRule = (event) => {
5330
5545
  event.preventDefault();
5331
5546
  const value = rule.trim();
@@ -5379,493 +5594,427 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5379
5594
  action: "block"
5380
5595
  }))
5381
5596
  ];
5382
- const enabled = config?.enabled ?? true;
5383
- const last = inspector?.last ?? null;
5384
- const effectiveMode = inspector?.config.mode ?? config?.mode ?? "blocklist";
5385
- const configuredMode = config?.mode ?? (config?.preset === "strict" ? "allowlist" : config?.preset === "audit-only" ? "audit" : "blocklist");
5386
- if (settings.status === "unavailable") return null;
5387
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
5388
- className: `dsh-plugin-card${open ? " dsh-plugin-card--open" : ""}`,
5389
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5390
- type: "button",
5391
- className: "dsh-plugin-card__header",
5392
- "aria-expanded": open,
5393
- "aria-label": `${open ? "Hide" : "Show"} settings: Prompt Firewall`,
5394
- onClick: () => {
5395
- setOpen(!open);
5396
- },
5397
- children: [
5398
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5399
- className: "dsh-plugin-card__head-text",
5400
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5401
- className: "dsh-plugin-card__name",
5402
- children: "Prompt Firewall"
5403
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5404
- className: "dsh-plugin-card__description",
5405
- children: "Prompt hygiene, section policy, and request-level observability."
5406
- })]
5407
- }),
5408
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5409
- className: "dsh-plugin-card__badge",
5410
- children: enabled ? "Enabled" : "Disabled"
5411
- }),
5412
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, {})
5413
- ]
5414
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5415
- className: "dsh-plugin-card__body pf-body",
5416
- children: [
5417
- error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5418
- className: "pf-error",
5419
- children: error
5420
- }),
5421
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5422
- className: "pf-section",
5423
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5424
- className: "pf-section-title",
5425
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Policy" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5426
- className: "pf-muted",
5427
- children: "Changes apply live"
5428
- })]
5429
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5430
- className: "pf-grid",
5597
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5598
+ className: "pf-section",
5599
+ children: [
5600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5601
+ className: "pf-section-title",
5602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Rules" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5603
+ className: "pf-muted",
5604
+ children: "Explicit rules; preset rules are applied in addition"
5605
+ })]
5606
+ }),
5607
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
5608
+ className: "pf-editor",
5609
+ onSubmit: addRule,
5610
+ children: [
5611
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5612
+ className: "pf-control",
5613
+ value: rule,
5614
+ disabled: !writable,
5615
+ placeholder: "plugin:example or announcement:*",
5616
+ onChange: (event) => {
5617
+ setRule(event.currentTarget.value);
5618
+ }
5619
+ }),
5620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5621
+ className: "pf-control",
5622
+ value: ruleKind,
5623
+ disabled: !writable || ruleAction === "protect",
5624
+ onChange: (event) => {
5625
+ setRuleKind(event.currentTarget.value);
5626
+ },
5431
5627
  children: [
5432
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5433
- checked: enabled,
5434
- disabled: !writable,
5435
- label: "Firewall enabled",
5436
- hint: "Keep auditing available when policy is off.",
5437
- onChange: (value) => {
5438
- setPath(["enabled"], value);
5439
- }
5628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5629
+ value: "exact",
5630
+ children: "Exact"
5440
5631
  }),
5441
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5442
- checked: config?.protectCoreSections ?? true,
5443
- disabled: !writable,
5444
- label: "Protect core sections",
5445
- hint: "Prevents ordinary rules from removing Harness internals.",
5446
- onChange: (value) => {
5447
- setPath(["protectCoreSections"], value);
5448
- }
5632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5633
+ value: "prefix",
5634
+ children: "Prefix"
5449
5635
  }),
5450
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5451
- className: "pf-field",
5452
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Mode" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5453
- className: "pf-control",
5454
- value: configuredMode,
5455
- disabled: !writable,
5456
- onChange: (event) => {
5457
- setPath(["mode"], event.currentTarget.value);
5458
- },
5459
- children: [
5460
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5461
- value: "off",
5462
- children: "Off"
5463
- }),
5464
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5465
- value: "audit",
5466
- children: "Audit only"
5467
- }),
5468
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5469
- value: "blocklist",
5470
- children: "Blocklist"
5471
- }),
5472
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5473
- value: "allowlist",
5474
- children: "Allowlist"
5475
- })
5476
- ]
5477
- })]
5478
- }),
5479
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5480
- className: "pf-field",
5481
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Preset" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5482
- className: "pf-control",
5483
- value: config?.preset ?? "",
5484
- disabled: !writable,
5485
- onChange: (event) => {
5486
- const value = event.currentTarget.value;
5487
- if (value === "") scope.unset("preset");
5488
- else setPath(["preset"], value);
5489
- },
5490
- children: [
5491
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5492
- value: "",
5493
- children: "None"
5494
- }),
5495
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5496
- value: "clean",
5497
- children: "Clean"
5498
- }),
5499
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5500
- value: "strict",
5501
- children: "Strict"
5502
- }),
5503
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5504
- value: "audit-only",
5505
- children: "Audit only"
5506
- })
5507
- ]
5508
- })]
5509
- }),
5510
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5511
- className: "pf-field",
5512
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Unknown plugin sections" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5513
- className: "pf-control",
5514
- value: config?.unknownPluginPolicy ?? "allow",
5515
- disabled: !writable,
5516
- onChange: (event) => {
5517
- setPath(["unknownPluginPolicy"], event.currentTarget.value);
5518
- },
5519
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5520
- value: "allow",
5521
- children: "Allow"
5522
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5523
- value: "block",
5524
- children: "Block"
5525
- })]
5526
- })]
5636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5637
+ value: "glob",
5638
+ children: "Glob"
5527
5639
  })
5528
5640
  ]
5529
- })]
5530
- }),
5531
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5532
- className: "pf-section",
5533
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5534
- className: "pf-section-title",
5535
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Last request" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5536
- className: "pf-btn",
5537
- disabled: refreshing,
5538
- onClick: () => {
5539
- refresh();
5540
- },
5541
- children: refreshing ? "Refreshing…" : "Refresh"
5542
- })]
5543
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5544
- className: "pf-stats",
5641
+ }),
5642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5643
+ className: "pf-control",
5644
+ value: ruleAction,
5645
+ disabled: !writable,
5646
+ onChange: (event) => {
5647
+ const action = event.currentTarget.value;
5648
+ setRuleAction(action);
5649
+ if (action === "protect") setRuleKind("exact");
5650
+ },
5545
5651
  children: [
5546
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5547
- className: "pf-stat",
5548
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: last?.totalSections ?? "—" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "sections" })]
5549
- }),
5550
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5551
- className: "pf-stat",
5552
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: last?.blockedSections ?? "—" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "blocked" })]
5652
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5653
+ value: "block",
5654
+ children: "Block"
5553
5655
  }),
5554
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5555
- className: "pf-stat",
5556
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: last === null ? "—" : `~${last.estimatedTokensRemoved}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "tokens removed (estimate)" })]
5656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5657
+ value: "allow",
5658
+ children: "Allow"
5557
5659
  }),
5558
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5559
- className: "pf-stat",
5560
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: effectiveMode }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "effective mode" })]
5660
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5661
+ value: "protect",
5662
+ children: "Protect"
5561
5663
  })
5562
5664
  ]
5665
+ }),
5666
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5667
+ className: "pf-btn primary",
5668
+ type: "submit",
5669
+ disabled: !writable || rule.trim().length === 0,
5670
+ children: "Add rule"
5671
+ })
5672
+ ]
5673
+ }),
5674
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5675
+ className: "pf-rules",
5676
+ children: [explicitRules.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5677
+ className: "pf-empty",
5678
+ children: "No explicit rules yet."
5679
+ }), explicitRules.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5680
+ className: "pf-rule",
5681
+ children: [
5682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
5683
+ title: item.value,
5684
+ children: item.value
5685
+ }),
5686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5687
+ className: "pf-pill pf-kind",
5688
+ children: item.kind
5689
+ }),
5690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5691
+ className: `pf-pill ${item.action}`,
5692
+ children: item.action
5693
+ }),
5694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5695
+ className: "pf-btn link danger",
5696
+ disabled: !writable,
5697
+ onClick: () => {
5698
+ setPath([item.field], list(config, item.field).filter((value) => value !== item.value));
5699
+ },
5700
+ children: "Remove"
5701
+ })
5702
+ ]
5703
+ }, `${item.field}:${item.value}`))]
5704
+ })
5705
+ ]
5706
+ });
5707
+ }
5708
+ function AuditSection(props) {
5709
+ const { config, writable, setPath } = props;
5710
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
5711
+ className: "pf-advanced",
5712
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: "Audit & metrics" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5713
+ className: "pf-advanced-content pf-grid",
5714
+ children: [
5715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5716
+ checked: config?.audit?.enabled ?? true,
5717
+ disabled: !writable,
5718
+ label: "Audit history",
5719
+ hint: "Keep recent assemblies in Host memory.",
5720
+ onChange: (value) => {
5721
+ setPath(["audit", "enabled"], value);
5722
+ }
5723
+ }),
5724
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5725
+ checked: config?.audit?.includePreview ?? false,
5726
+ disabled: !writable,
5727
+ label: "Section preview",
5728
+ hint: "Expose only the configured prefix in Inspector.",
5729
+ onChange: (value) => {
5730
+ setPath(["audit", "includePreview"], value);
5731
+ }
5732
+ }),
5733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5734
+ checked: config?.audit?.logBlocked ?? true,
5735
+ disabled: !writable,
5736
+ label: "Log blocked sections",
5737
+ hint: "Names and sizes only unless preview is enabled.",
5738
+ onChange: (value) => {
5739
+ setPath(["audit", "logBlocked"], value);
5740
+ }
5741
+ }),
5742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5743
+ checked: config?.audit?.logAllowed ?? false,
5744
+ disabled: !writable,
5745
+ label: "Log allowed sections",
5746
+ hint: "Useful for initial policy discovery.",
5747
+ onChange: (value) => {
5748
+ setPath(["audit", "logAllowed"], value);
5749
+ }
5750
+ }),
5751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5752
+ checked: config?.audit?.highlightNewSections ?? true,
5753
+ disabled: !writable,
5754
+ label: "Highlight new plugin sections",
5755
+ hint: "Informational only; never blocks automatically.",
5756
+ onChange: (value) => {
5757
+ setPath(["audit", "highlightNewSections"], value);
5758
+ }
5759
+ }),
5760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5761
+ checked: config?.metrics?.enabled ?? true,
5762
+ disabled: !writable,
5763
+ label: "Aggregate metrics",
5764
+ hint: "No arbitrary section-name labels.",
5765
+ onChange: (value) => {
5766
+ setPath(["metrics", "enabled"], value);
5767
+ }
5768
+ }),
5769
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5770
+ className: "pf-field",
5771
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Preview characters" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5772
+ className: "pf-control",
5773
+ type: "number",
5774
+ min: "0",
5775
+ step: "1",
5776
+ value: config?.audit?.previewChars ?? 160,
5777
+ disabled: !writable,
5778
+ onChange: (event) => {
5779
+ setPath(["audit", "previewChars"], Number(event.currentTarget.value));
5780
+ }
5563
5781
  })]
5564
5782
  }),
5565
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5566
- className: "pf-section",
5567
- children: [
5568
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5569
- className: "pf-section-title",
5570
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Rules" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5783
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5784
+ className: "pf-field",
5785
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "History size" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5786
+ className: "pf-control",
5787
+ type: "number",
5788
+ min: "0",
5789
+ step: "1",
5790
+ value: config?.audit?.historySize ?? 100,
5791
+ disabled: !writable,
5792
+ onChange: (event) => {
5793
+ setPath(["audit", "historySize"], Number(event.currentTarget.value));
5794
+ }
5795
+ })]
5796
+ })
5797
+ ]
5798
+ })]
5799
+ });
5800
+ }
5801
+ function InspectorSection(props) {
5802
+ const last = props.inspector?.last ?? null;
5803
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5804
+ className: "pf-section",
5805
+ children: [
5806
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5807
+ className: "pf-section-title",
5808
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Prompt Inspector" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5809
+ className: "pf-muted",
5810
+ children: "Updates every 3 seconds"
5811
+ })]
5812
+ }),
5813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5814
+ className: "pf-table-wrap",
5815
+ children: last === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5816
+ className: "pf-empty",
5817
+ children: "No audited prompt assembly yet."
5818
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
5819
+ className: "pf-table",
5820
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
5821
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Section" }),
5822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Size" }),
5823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Decision" }),
5824
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Reason" }),
5825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Policy" })
5826
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: last.sections.map((section, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
5827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5828
+ className: "pf-section-name",
5829
+ children: [
5830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: section.name }),
5831
+ section.isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5832
+ className: "pf-new",
5833
+ children: "NEW"
5834
+ }),
5835
+ section.suspicious && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5836
+ className: "pf-warn",
5837
+ children: "POSSIBLE ANNOUNCEMENT"
5838
+ })
5839
+ ]
5840
+ }), section.preview !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5841
+ className: "pf-preview",
5842
+ children: section.preview
5843
+ })] }),
5844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
5845
+ "~",
5846
+ section.estimatedTokens,
5847
+ " t",
5848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
5849
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5571
5850
  className: "pf-muted",
5572
- children: "Explicit rules; preset rules are applied in addition"
5573
- })]
5574
- }),
5575
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
5576
- className: "pf-editor",
5577
- onSubmit: addRule,
5851
+ children: [section.chars, " chars"]
5852
+ })
5853
+ ] }),
5854
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5855
+ className: `pf-pill ${section.decision === "blocked" ? "block" : section.decision === "protected" ? "protect" : "allow"}`,
5856
+ children: section.decision
5857
+ }) }),
5858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: section.reason }),
5859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5860
+ className: "pf-actions",
5578
5861
  children: [
5579
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5580
- className: "pf-control",
5581
- value: rule,
5582
- disabled: !writable,
5583
- placeholder: "plugin:example or announcement:*",
5584
- onChange: (event) => {
5585
- setRule(event.currentTarget.value);
5586
- }
5862
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5863
+ className: "pf-btn link",
5864
+ disabled: !props.writable,
5865
+ onClick: () => {
5866
+ props.setPolicy(section.name, "allow");
5867
+ },
5868
+ children: "Allow"
5587
5869
  }),
5588
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5589
- className: "pf-control",
5590
- value: ruleKind,
5591
- disabled: !writable || ruleAction === "protect",
5592
- onChange: (event) => {
5593
- setRuleKind(event.currentTarget.value);
5870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5871
+ className: "pf-btn link danger",
5872
+ disabled: !props.writable,
5873
+ onClick: () => {
5874
+ props.setPolicy(section.name, "block");
5594
5875
  },
5595
- children: [
5596
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5597
- value: "exact",
5598
- children: "Exact"
5599
- }),
5600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5601
- value: "prefix",
5602
- children: "Prefix"
5603
- }),
5604
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5605
- value: "glob",
5606
- children: "Glob"
5607
- })
5608
- ]
5876
+ children: "Block"
5609
5877
  }),
5610
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5611
- className: "pf-control",
5612
- value: ruleAction,
5613
- disabled: !writable,
5614
- onChange: (event) => {
5615
- const action = event.currentTarget.value;
5616
- setRuleAction(action);
5617
- if (action === "protect") setRuleKind("exact");
5878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5879
+ className: "pf-btn link",
5880
+ disabled: !props.writable,
5881
+ onClick: () => {
5882
+ props.setPolicy(section.name, "protect");
5618
5883
  },
5619
- children: [
5620
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5621
- value: "block",
5622
- children: "Block"
5623
- }),
5624
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5625
- value: "allow",
5626
- children: "Allow"
5627
- }),
5628
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5629
- value: "protect",
5630
- children: "Protect"
5631
- })
5632
- ]
5884
+ children: "Protect"
5633
5885
  }),
5634
5886
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5635
- className: "pf-btn primary",
5636
- type: "submit",
5637
- disabled: !writable || rule.trim().length === 0,
5638
- children: "Add rule"
5887
+ className: "pf-btn link",
5888
+ disabled: !props.writable,
5889
+ onClick: () => {
5890
+ props.setPolicy(section.name, "clear");
5891
+ },
5892
+ children: "Clear"
5639
5893
  })
5640
5894
  ]
5641
- }),
5642
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5643
- className: "pf-rules",
5644
- children: [explicitRules.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5645
- className: "pf-empty",
5646
- children: "No explicit rules yet."
5647
- }), explicitRules.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5648
- className: "pf-rule",
5649
- children: [
5650
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
5651
- title: item.value,
5652
- children: item.value
5653
- }),
5654
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5655
- className: "pf-pill pf-kind",
5656
- children: item.kind
5657
- }),
5658
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5659
- className: `pf-pill ${item.action}`,
5660
- children: item.action
5661
- }),
5662
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5663
- className: "pf-btn link danger",
5664
- disabled: !writable,
5665
- onClick: () => {
5666
- setPath([item.field], list(config, item.field).filter((value) => value !== item.value));
5667
- },
5668
- children: "Remove"
5669
- })
5670
- ]
5671
- }, `${item.field}:${item.value}`))]
5672
- })
5673
- ]
5674
- }),
5675
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
5676
- className: "pf-advanced",
5677
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: "Audit & metrics" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5678
- className: "pf-advanced-content pf-grid",
5679
- children: [
5680
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5681
- checked: config?.audit?.enabled ?? true,
5682
- disabled: !writable,
5683
- label: "Audit history",
5684
- hint: "Keep recent assemblies in Host memory.",
5685
- onChange: (value) => {
5686
- setPath(["audit", "enabled"], value);
5687
- }
5688
- }),
5689
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5690
- checked: config?.audit?.includePreview ?? false,
5691
- disabled: !writable,
5692
- label: "Section preview",
5693
- hint: "Expose only the configured prefix in Inspector.",
5694
- onChange: (value) => {
5695
- setPath(["audit", "includePreview"], value);
5696
- }
5697
- }),
5698
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5699
- checked: config?.audit?.logBlocked ?? true,
5700
- disabled: !writable,
5701
- label: "Log blocked sections",
5702
- hint: "Names and sizes only unless preview is enabled.",
5703
- onChange: (value) => {
5704
- setPath(["audit", "logBlocked"], value);
5705
- }
5706
- }),
5707
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5708
- checked: config?.audit?.logAllowed ?? false,
5709
- disabled: !writable,
5710
- label: "Log allowed sections",
5711
- hint: "Useful for initial policy discovery.",
5712
- onChange: (value) => {
5713
- setPath(["audit", "logAllowed"], value);
5714
- }
5715
- }),
5716
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5717
- checked: config?.audit?.highlightNewSections ?? true,
5718
- disabled: !writable,
5719
- label: "Highlight new plugin sections",
5720
- hint: "Informational only; never blocks automatically.",
5721
- onChange: (value) => {
5722
- setPath(["audit", "highlightNewSections"], value);
5723
- }
5724
- }),
5725
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
5726
- checked: config?.metrics?.enabled ?? true,
5727
- disabled: !writable,
5728
- label: "Aggregate metrics",
5729
- hint: "No arbitrary section-name labels.",
5730
- onChange: (value) => {
5731
- setPath(["metrics", "enabled"], value);
5732
- }
5733
- }),
5734
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5735
- className: "pf-field",
5736
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Preview characters" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5737
- className: "pf-control",
5738
- type: "number",
5739
- min: "0",
5740
- step: "1",
5741
- value: config?.audit?.previewChars ?? 160,
5742
- disabled: !writable,
5743
- onChange: (event) => {
5744
- setPath(["audit", "previewChars"], Number(event.currentTarget.value));
5745
- }
5746
- })]
5747
- }),
5748
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5749
- className: "pf-field",
5750
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "History size" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5751
- className: "pf-control",
5752
- type: "number",
5753
- min: "0",
5754
- step: "1",
5755
- value: config?.audit?.historySize ?? 100,
5756
- disabled: !writable,
5757
- onChange: (event) => {
5758
- setPath(["audit", "historySize"], Number(event.currentTarget.value));
5759
- }
5760
- })]
5761
- })
5762
- ]
5763
- })]
5764
- }),
5765
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5766
- className: "pf-section",
5767
- children: [
5768
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5769
- className: "pf-section-title",
5770
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Prompt Inspector" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5771
- className: "pf-muted",
5772
- children: "Updates every 3 seconds"
5773
- })]
5774
- }),
5775
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5776
- className: "pf-table-wrap",
5777
- children: last === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5778
- className: "pf-empty",
5779
- children: "No audited prompt assembly yet."
5780
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
5781
- className: "pf-table",
5782
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
5783
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Section" }),
5784
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Size" }),
5785
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Decision" }),
5786
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Reason" }),
5787
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Policy" })
5788
- ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: last.sections.map((section, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
5789
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5790
- className: "pf-section-name",
5791
- children: [
5792
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: section.name }),
5793
- section.isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5794
- className: "pf-new",
5795
- children: "NEW"
5796
- }),
5797
- section.suspicious && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5798
- className: "pf-warn",
5799
- children: "POSSIBLE ANNOUNCEMENT"
5800
- })
5801
- ]
5802
- }), section.preview !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5803
- className: "pf-preview",
5804
- children: section.preview
5805
- })] }),
5806
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
5807
- "~",
5808
- section.estimatedTokens,
5809
- " t",
5810
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
5811
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5812
- className: "pf-muted",
5813
- children: [section.chars, " chars"]
5814
- })
5815
- ] }),
5816
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5817
- className: `pf-pill ${section.decision === "blocked" ? "block" : section.decision === "protected" ? "protect" : "allow"}`,
5818
- children: section.decision
5819
- }) }),
5820
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: section.reason }),
5821
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5822
- className: "pf-actions",
5823
- children: [
5824
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5825
- className: "pf-btn link",
5826
- disabled: !writable,
5827
- onClick: () => {
5828
- setPolicy(section.name, "allow");
5829
- },
5830
- children: "Allow"
5831
- }),
5832
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5833
- className: "pf-btn link danger",
5834
- disabled: !writable,
5835
- onClick: () => {
5836
- setPolicy(section.name, "block");
5837
- },
5838
- children: "Block"
5839
- }),
5840
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5841
- className: "pf-btn link",
5842
- disabled: !writable,
5843
- onClick: () => {
5844
- setPolicy(section.name, "protect");
5845
- },
5846
- children: "Protect"
5847
- }),
5848
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5849
- className: "pf-btn link",
5850
- disabled: !writable,
5851
- onClick: () => {
5852
- setPolicy(section.name, "clear");
5853
- },
5854
- children: "Clear"
5855
- })
5856
- ]
5857
- }) })
5858
- ] }, `${section.name}:${index}`)) })]
5859
- })
5860
- }),
5861
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5862
- className: "pf-footer-note",
5863
- children: "Token counts are estimates. Prompt Firewall is a hygiene and observability layer, not a security boundary."
5864
- })
5865
- ]
5895
+ }) })
5896
+ ] }, `${section.name}:${index}`)) })]
5866
5897
  })
5867
- ]
5868
- }) : null]
5898
+ }),
5899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5900
+ className: "pf-footer-note",
5901
+ children: "Token counts are estimates. Prompt Firewall is a hygiene and observability layer, not a security boundary."
5902
+ })
5903
+ ]
5904
+ });
5905
+ }
5906
+ //#endregion
5907
+ //#region src/client/index.tsx
5908
+ const SETTINGS_NAMESPACE = "prompt-firewall";
5909
+ const REFRESH_INTERVAL_MS = 3e3;
5910
+ function displayError(error) {
5911
+ if (error instanceof Error) return error.message;
5912
+ if (typeof error === "string") return error;
5913
+ return "Could not load Prompt Inspector data.";
5914
+ }
5915
+ function PromptFirewallCard({ scope, inspect, setSectionPolicy }) {
5916
+ const settingsStore = (0, react.useMemo)(() => bindSettingsExternalStore(scope), [scope]);
5917
+ const settings = (0, react.useSyncExternalStore)(settingsStore.subscribe, settingsStore.getSnapshot, settingsStore.getSnapshot);
5918
+ const config = settings.value;
5919
+ const writable = settings.status === "ready" && settings.writable;
5920
+ const [inspector, setInspector] = (0, react.useState)(null);
5921
+ const [error, setError] = (0, react.useState)(null);
5922
+ const [refreshing, setRefreshing] = (0, react.useState)(false);
5923
+ const activeRequest = (0, react.useRef)(0);
5924
+ const refresh = (0, react.useCallback)(async () => {
5925
+ const request = ++activeRequest.current;
5926
+ setRefreshing(true);
5927
+ try {
5928
+ const result = await inspect();
5929
+ if (request !== activeRequest.current) return;
5930
+ if (result.ok) {
5931
+ setInspector(result.value);
5932
+ setError(null);
5933
+ } else setError(displayError(result.error));
5934
+ } catch (cause) {
5935
+ if (request === activeRequest.current) setError(displayError(cause));
5936
+ } finally {
5937
+ if (request === activeRequest.current) setRefreshing(false);
5938
+ }
5939
+ }, [inspect]);
5940
+ (0, react.useEffect)(() => {
5941
+ const stopPolling = startVisibilityAwarePolling(refresh, REFRESH_INTERVAL_MS);
5942
+ return () => {
5943
+ stopPolling();
5944
+ activeRequest.current += 1;
5945
+ };
5946
+ }, [refresh]);
5947
+ const setPath = (0, react.useCallback)((path, value) => {
5948
+ const [field, nested] = path;
5949
+ if (field === void 0) return;
5950
+ if (nested === void 0) {
5951
+ scope.set(field, value);
5952
+ return;
5953
+ }
5954
+ const current = scope.getSnapshot().value;
5955
+ const parent = field === "audit" ? current?.audit : current?.metrics;
5956
+ scope.set(field, {
5957
+ ...parent,
5958
+ [nested]: value
5959
+ });
5960
+ }, [scope]);
5961
+ const setPolicy = (0, react.useCallback)(async (name, policy) => {
5962
+ const result = await setSectionPolicy(name, policy, scope.getSnapshot().revision);
5963
+ if (!result.ok) setError(displayError(result.error));
5964
+ await refresh();
5965
+ }, [
5966
+ refresh,
5967
+ scope,
5968
+ setSectionPolicy
5969
+ ]);
5970
+ const enabled = config?.enabled ?? true;
5971
+ if (settings.status === "unavailable") return null;
5972
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardShell, {
5973
+ title: "Prompt Firewall",
5974
+ description: "Prompt hygiene, section policy, and request-level observability.",
5975
+ badge: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5976
+ className: "dsh-plugin-card__badge",
5977
+ children: enabled ? "Enabled" : "Disabled"
5978
+ }),
5979
+ label: (open) => `${open ? "Hide" : "Show"} settings: Prompt Firewall`,
5980
+ bodyClassName: "pf-body",
5981
+ children: [
5982
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5983
+ className: "pf-error",
5984
+ children: error
5985
+ }),
5986
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolicySection, {
5987
+ config,
5988
+ writable,
5989
+ setPath,
5990
+ unsetPreset: () => {
5991
+ scope.unset("preset");
5992
+ }
5993
+ }),
5994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastRequestSection, {
5995
+ config,
5996
+ inspector,
5997
+ refreshing,
5998
+ onRefresh: () => {
5999
+ refresh();
6000
+ }
6001
+ }),
6002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RulesSection, {
6003
+ config,
6004
+ writable,
6005
+ setPath
6006
+ }),
6007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditSection, {
6008
+ config,
6009
+ writable,
6010
+ setPath
6011
+ }),
6012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InspectorSection, {
6013
+ inspector,
6014
+ writable,
6015
+ setPolicy
6016
+ })
6017
+ ]
5869
6018
  });
5870
6019
  }
5871
6020
  const inject = [
@@ -5884,19 +6033,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5884
6033
  inspect: () => injectedRemote.promptFirewall.inspect(),
5885
6034
  setSectionPolicy: (section, policy, revision) => injectedRemote.promptFirewall.setSectionPolicy(section, policy, revision)
5886
6035
  };
5887
- const style = document.createElement("style");
5888
- style.dataset.plugin = "dsh-prompt-firewall";
5889
- style.textContent = styles;
5890
- document.head.appendChild(style);
5891
- const disposeSlot = remoteCtx.slots.inject("settings.plugin.item", () => remoteCtx.slots.register({
5892
- name: "settings.plugin.item",
6036
+ return registerSettingsCard(remoteCtx, {
5893
6037
  key: SETTINGS_NAMESPACE,
6038
+ pluginName: "dsh-prompt-firewall",
6039
+ styles,
6040
+ component: PromptFirewallCard,
5894
6041
  inject: () => face
5895
- }, PromptFirewallCard));
5896
- return () => {
5897
- disposeSlot();
5898
- style.remove();
5899
- };
6042
+ });
5900
6043
  });
5901
6044
  } catch (cause) {
5902
6045
  await disposeRemote();