dsh-pentester 2.2.0 → 2.4.0

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
@@ -40,7 +40,7 @@ __export(plugin_exports, {
40
40
  setPentesterEndpointState: () => setPentesterEndpointState
41
41
  });
42
42
  module.exports = __toCommonJS(plugin_exports);
43
- var import_react19 = __toESM(require("react"), 1);
43
+ var import_react20 = __toESM(require("react"), 1);
44
44
 
45
45
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
46
46
  var external_exports = {};
@@ -15397,7 +15397,7 @@ function isPentesterRootSession(snapshot, id) {
15397
15397
  }
15398
15398
 
15399
15399
  // src/ui/client/pentester-view.tsx
15400
- var import_react18 = __toESM(require("react"), 1);
15400
+ var import_react19 = __toESM(require("react"), 1);
15401
15401
 
15402
15402
  // src/ui/client/pentester/navigation.ts
15403
15403
  var PENTESTER_TABS = [
@@ -16100,8 +16100,71 @@ function LegendItem(props) {
16100
16100
  }
16101
16101
 
16102
16102
  // src/ui/client/pentester/FindingsView.tsx
16103
+ var import_react11 = __toESM(require("react"), 1);
16104
+ var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
16105
+
16106
+ // src/ui/client/pentester/CopyAction.tsx
16103
16107
  var import_react10 = __toESM(require("react"), 1);
16104
16108
  var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
16109
+ function CopyAction(props) {
16110
+ const { text } = props;
16111
+ const [copied, setCopied] = (0, import_react10.useState)(false);
16112
+ const timerRef = (0, import_react10.useRef)(null);
16113
+ const pendingRef = (0, import_react10.useRef)(false);
16114
+ const handleCopy = (0, import_react10.useCallback)(() => {
16115
+ if (copied || pendingRef.current) return;
16116
+ pendingRef.current = true;
16117
+ void (0, import_dsh_client_ui_primitives3.writeClipboard)(text).then((ok) => {
16118
+ pendingRef.current = false;
16119
+ if (!ok) return;
16120
+ setCopied(true);
16121
+ timerRef.current = setTimeout(() => {
16122
+ setCopied(false);
16123
+ timerRef.current = null;
16124
+ }, 1e3);
16125
+ });
16126
+ }, [text, copied]);
16127
+ (0, import_react10.useEffect)(() => {
16128
+ return () => {
16129
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
16130
+ };
16131
+ }, []);
16132
+ return import_react10.default.createElement(
16133
+ "button",
16134
+ {
16135
+ type: "button",
16136
+ onClick: handleCopy,
16137
+ "aria-label": copied ? "Copied" : "Copy",
16138
+ style: {
16139
+ display: "inline-flex",
16140
+ alignItems: "center",
16141
+ gap: "6px",
16142
+ padding: "4px 8px",
16143
+ border: "none",
16144
+ borderRadius: "6px",
16145
+ background: "transparent",
16146
+ color: "var(--dsw-alias-label-secondary)",
16147
+ cursor: "pointer",
16148
+ fontSize: "13px",
16149
+ lineHeight: "16px",
16150
+ whiteSpace: "nowrap",
16151
+ flexShrink: 0
16152
+ },
16153
+ onMouseEnter: (e) => {
16154
+ e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover)";
16155
+ e.currentTarget.style.color = "var(--dsw-alias-label-primary)";
16156
+ },
16157
+ onMouseLeave: (e) => {
16158
+ e.currentTarget.style.background = "transparent";
16159
+ e.currentTarget.style.color = "var(--dsw-alias-label-secondary)";
16160
+ }
16161
+ },
16162
+ copied ? import_react10.default.createElement(import_dsh_client_ui_primitives3.IconCheckOutline16, { size: 16 }) : import_react10.default.createElement(import_dsh_client_ui_primitives3.IconCopyOutline16, { size: 16 }),
16163
+ copied ? "Copied" : "Copy"
16164
+ );
16165
+ }
16166
+
16167
+ // src/ui/client/pentester/FindingsView.tsx
16105
16168
  var SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
16106
16169
  var SEVERITY_LABELS = {
16107
16170
  critical: "Critical",
@@ -16117,176 +16180,594 @@ var SEVERITY_COLORS = {
16117
16180
  low: "#3b82f6",
16118
16181
  info: "#6b7280"
16119
16182
  };
16120
- function ExpandableSection(props) {
16121
- const [open, setOpen] = (0, import_react10.useState)(props.defaultOpen);
16122
- return import_react10.default.createElement(
16123
- "div",
16124
- { style: { marginBottom: "4px" } },
16125
- import_react10.default.createElement(
16126
- "div",
16127
- {
16128
- onClick: () => setOpen(!open),
16129
- style: {
16130
- display: "flex",
16131
- alignItems: "center",
16132
- gap: "6px",
16133
- padding: "6px 8px",
16134
- cursor: "pointer",
16135
- borderRadius: "4px",
16136
- fontSize: "13px",
16137
- fontWeight: 600,
16138
- color: "var(--dsw-fg)",
16139
- userSelect: "none"
16140
- }
16141
- },
16142
- import_react10.default.createElement("span", {
16143
- style: { fontSize: "12px", transition: "transform 0.15s", transform: open ? "rotate(90deg)" : "rotate(0deg)" }
16144
- }, "\u25B6"),
16145
- import_react10.default.createElement("span", null, props.title)
16146
- ),
16147
- open && import_react10.default.createElement("div", { style: { paddingLeft: "20px" } }, props.children)
16148
- );
16149
- }
16183
+ var FILTER_TABS = [
16184
+ { id: "all", label: "All" },
16185
+ ...SEVERITY_ORDER.map((s) => ({ id: s, label: SEVERITY_LABELS[s] }))
16186
+ ];
16150
16187
  function FindingsView(props) {
16151
- const { snapshot, command, sessionId } = props;
16152
- const [selectedFinding, setSelectedFinding] = (0, import_react10.useState)(null);
16153
- const [detailContent, setDetailContent] = (0, import_react10.useState)(null);
16154
- const [detailLoading, setDetailLoading] = (0, import_react10.useState)(false);
16188
+ const { snapshot, command, sessionId, onNavigate } = props;
16155
16189
  const findings = snapshot?.findings ?? [];
16156
- const grouped = (0, import_react10.useMemo)(() => {
16157
- const groups = /* @__PURE__ */ new Map();
16158
- for (const severity of SEVERITY_ORDER) {
16159
- groups.set(severity, []);
16160
- }
16161
- for (const f of findings) {
16162
- const list = groups.get(f.severity);
16163
- if (list !== void 0) list.push(f);
16164
- }
16165
- return groups;
16190
+ const [selectedId, setSelectedId] = (0, import_react11.useState)(null);
16191
+ const [detailContent, setDetailContent] = (0, import_react11.useState)(null);
16192
+ const [detailLoading, setDetailLoading] = (0, import_react11.useState)(false);
16193
+ const [detailError, setDetailError] = (0, import_react11.useState)(false);
16194
+ const [filter, setFilter] = (0, import_react11.useState)("all");
16195
+ const [search, setSearch] = (0, import_react11.useState)("");
16196
+ const [focusDone, setFocusDone] = (0, import_react11.useState)(false);
16197
+ const loadSeqRef = (0, import_react11.useRef)(0);
16198
+ const severityCounts = (0, import_react11.useMemo)(() => {
16199
+ const counts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
16200
+ for (const f of findings) counts[f.severity]++;
16201
+ return counts;
16166
16202
  }, [findings]);
16167
- const openDetail = (0, import_react10.useCallback)(async (finding) => {
16168
- setSelectedFinding(finding);
16203
+ const filtered = (0, import_react11.useMemo)(() => {
16204
+ let list = [...findings];
16205
+ if (filter !== "all") list = list.filter((f) => f.severity === filter);
16206
+ if (search.trim() !== "") {
16207
+ const q = search.toLowerCase();
16208
+ list = list.filter(
16209
+ (f) => f.title.toLowerCase().includes(q) || (f.affectedAsset?.toLowerCase().includes(q) ?? false) || f.sourceStage.toLowerCase().includes(q) || f.sourceDelegationId.toLowerCase().includes(q)
16210
+ );
16211
+ }
16212
+ const orderMap = new Map(SEVERITY_ORDER.map((s, i) => [s, i]));
16213
+ list.sort((a, b) => {
16214
+ const oa = orderMap.get(a.severity) ?? 99;
16215
+ const ob = orderMap.get(b.severity) ?? 99;
16216
+ if (oa !== ob) return oa - ob;
16217
+ return 0;
16218
+ });
16219
+ return list;
16220
+ }, [findings, filter, search]);
16221
+ const selectedFinding = (0, import_react11.useMemo)(
16222
+ () => selectedId !== null ? findings.find((f) => f.id === selectedId) ?? null : null,
16223
+ [findings, selectedId]
16224
+ );
16225
+ (0, import_react11.useEffect)(() => {
16226
+ if (props.focusFinding !== void 0 && !focusDone) {
16227
+ const match = findings.find((f) => f.id === props.focusFinding);
16228
+ if (match !== void 0) {
16229
+ setSelectedId(match.id);
16230
+ setFilter("all");
16231
+ setSearch("");
16232
+ setFocusDone(true);
16233
+ }
16234
+ }
16235
+ }, [props.focusFinding, focusDone, findings]);
16236
+ (0, import_react11.useEffect)(() => {
16237
+ setFocusDone(false);
16238
+ }, [props.focusFinding]);
16239
+ const loadDetail = (0, import_react11.useCallback)(async (finding) => {
16240
+ setSelectedId(finding.id);
16169
16241
  setDetailContent(null);
16170
16242
  setDetailLoading(true);
16243
+ setDetailError(false);
16244
+ const seq = ++loadSeqRef.current;
16171
16245
  try {
16172
16246
  const result = await command({
16173
16247
  kind: "readFinding",
16174
16248
  detailFile: finding.detailFile,
16175
16249
  sessionId
16176
16250
  });
16251
+ if (seq !== loadSeqRef.current) return;
16177
16252
  if (result.ok) {
16178
16253
  const fileResult = result.value;
16179
16254
  setDetailContent(fileResult.content ?? "");
16255
+ setDetailError(false);
16180
16256
  } else {
16181
- setDetailContent("*Unable to load finding detail.*");
16257
+ setDetailContent(null);
16258
+ setDetailError(true);
16182
16259
  }
16183
16260
  } catch {
16184
- setDetailContent("*Error loading finding detail.*");
16261
+ if (seq !== loadSeqRef.current) return;
16262
+ setDetailContent(null);
16263
+ setDetailError(true);
16185
16264
  } finally {
16186
- setDetailLoading(false);
16265
+ if (seq === loadSeqRef.current) setDetailLoading(false);
16187
16266
  }
16188
16267
  }, [command, sessionId]);
16189
- const closeDetail = (0, import_react10.useCallback)(() => {
16190
- setSelectedFinding(null);
16191
- setDetailContent(null);
16192
- }, []);
16193
- if (findings.length === 0) {
16194
- return import_react10.default.createElement("div", {
16195
- style: { padding: "24px", color: "var(--dsw-fg-muted)", fontSize: "14px" }
16196
- }, "No findings yet");
16197
- }
16198
- const severitySections = SEVERITY_ORDER.map((severity) => {
16199
- const items = grouped.get(severity) ?? [];
16200
- if (items.length === 0) return null;
16201
- const findingRows = items.map(
16202
- (finding) => import_react10.default.createElement(
16268
+ return import_react11.default.createElement(
16269
+ "div",
16270
+ {
16271
+ style: {
16272
+ display: "flex",
16273
+ flexDirection: "column",
16274
+ height: "100%",
16275
+ minHeight: 0,
16276
+ overflow: "hidden"
16277
+ }
16278
+ },
16279
+ // ── Top: severity summary strip ──────────────────────────────────────
16280
+ import_react11.default.createElement(SeveritySummary, {
16281
+ total: findings.length,
16282
+ counts: severityCounts,
16283
+ activeFilter: filter,
16284
+ onFilter: setFilter
16285
+ }),
16286
+ // ── Bottom: split view ───────────────────────────────────────────────
16287
+ import_react11.default.createElement(
16288
+ "div",
16289
+ {
16290
+ style: {
16291
+ flex: 1,
16292
+ minHeight: 0,
16293
+ display: "flex",
16294
+ overflow: "hidden"
16295
+ }
16296
+ },
16297
+ // ── Left: Navigator ───────────────────────────────────────────────
16298
+ import_react11.default.createElement(
16203
16299
  "div",
16204
16300
  {
16205
- key: finding.id,
16206
16301
  style: {
16302
+ flex: "0 0 340px",
16303
+ minWidth: "280px",
16304
+ maxWidth: "380px",
16305
+ minHeight: 0,
16207
16306
  display: "flex",
16307
+ flexDirection: "column",
16308
+ overflow: "hidden",
16309
+ borderRight: "1px solid var(--dsw-alias-border-l2)"
16310
+ }
16311
+ },
16312
+ // Search + filter (fixed header)
16313
+ import_react11.default.createElement(
16314
+ "div",
16315
+ {
16316
+ style: {
16317
+ flex: "none",
16318
+ padding: "8px 12px 4px"
16319
+ }
16320
+ },
16321
+ import_react11.default.createElement("input", {
16322
+ type: "text",
16323
+ placeholder: "Search findings...",
16324
+ value: search,
16325
+ onChange: (e) => setSearch(e.target.value),
16326
+ style: {
16327
+ width: "100%",
16328
+ boxSizing: "border-box",
16329
+ padding: "6px 10px",
16330
+ border: "1px solid var(--dsw-alias-border-l2)",
16331
+ borderRadius: "6px",
16332
+ background: "var(--dsw-alias-bg-base)",
16333
+ color: "var(--dsw-alias-label-primary)",
16334
+ fontSize: "13px",
16335
+ outline: "none",
16336
+ marginBottom: "4px"
16337
+ }
16338
+ }),
16339
+ import_react11.default.createElement(PentesterTabBar, {
16340
+ items: FILTER_TABS,
16341
+ active: filter,
16342
+ onSelect: (id) => setFilter(id),
16343
+ variant: "compact"
16344
+ })
16345
+ ),
16346
+ // FindingList (scrollable)
16347
+ import_react11.default.createElement(
16348
+ "div",
16349
+ {
16350
+ style: {
16351
+ flex: 1,
16352
+ minHeight: 0,
16353
+ overflowY: "auto",
16354
+ overflowX: "hidden",
16355
+ overscrollBehaviorY: "contain",
16356
+ scrollbarGutter: "stable"
16357
+ }
16358
+ },
16359
+ filtered.length === 0 ? import_react11.default.createElement("div", {
16360
+ style: {
16361
+ padding: "24px 12px",
16362
+ color: "var(--dsw-alias-label-tertiary)",
16363
+ fontSize: "13px",
16364
+ textAlign: "center"
16365
+ }
16366
+ }, findings.length === 0 ? "No findings yet" : "No matching findings") : filtered.map(
16367
+ (finding) => import_react11.default.createElement(FindingRow, {
16368
+ key: finding.id,
16369
+ finding,
16370
+ isSelected: finding.id === selectedId,
16371
+ onClick: () => loadDetail(finding)
16372
+ })
16373
+ )
16374
+ )
16375
+ ),
16376
+ // ── Right: Detail ──────────────────────────────────────────────────
16377
+ import_react11.default.createElement(
16378
+ "div",
16379
+ {
16380
+ style: {
16381
+ flex: 1,
16382
+ minWidth: 0,
16383
+ minHeight: 0,
16384
+ display: "flex",
16385
+ flexDirection: "column",
16386
+ overflow: "hidden"
16387
+ }
16388
+ },
16389
+ selectedFinding === null ? import_react11.default.createElement(
16390
+ "div",
16391
+ {
16392
+ style: {
16393
+ flex: 1,
16394
+ display: "flex",
16395
+ alignItems: "center",
16396
+ justifyContent: "center",
16397
+ flexDirection: "column",
16398
+ gap: "8px",
16399
+ color: "var(--dsw-alias-label-tertiary)",
16400
+ fontSize: "13px"
16401
+ }
16402
+ },
16403
+ findings.length === 0 ? import_react11.default.createElement(
16404
+ import_react11.default.Fragment,
16405
+ null,
16406
+ import_react11.default.createElement("div", { style: { fontWeight: 500, color: "var(--dsw-alias-label-primary)" } }, "No findings yet"),
16407
+ import_react11.default.createElement("div", null, "Validated findings will appear here as the assessment progresses.")
16408
+ ) : "Select a finding to inspect"
16409
+ ) : import_react11.default.createElement(FindingDetail, {
16410
+ finding: selectedFinding,
16411
+ content: detailContent,
16412
+ loading: detailLoading,
16413
+ error: detailError,
16414
+ onNavigate
16415
+ })
16416
+ )
16417
+ )
16418
+ );
16419
+ }
16420
+ function SeveritySummary(props) {
16421
+ const { total, counts, activeFilter, onFilter } = props;
16422
+ const items = [
16423
+ { key: "all", label: "Total", count: total },
16424
+ ...SEVERITY_ORDER.map((s) => ({
16425
+ key: s,
16426
+ label: SEVERITY_LABELS[s],
16427
+ count: counts[s],
16428
+ color: SEVERITY_COLORS[s]
16429
+ }))
16430
+ ];
16431
+ return import_react11.default.createElement(
16432
+ "div",
16433
+ {
16434
+ style: {
16435
+ flex: "none",
16436
+ display: "flex",
16437
+ alignItems: "center",
16438
+ gap: "4px",
16439
+ padding: "8px 12px",
16440
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
16441
+ flexWrap: "wrap",
16442
+ fontSize: "13px",
16443
+ color: "var(--dsw-alias-label-secondary)"
16444
+ }
16445
+ },
16446
+ ...items.map((item) => {
16447
+ const isActive = activeFilter === item.key;
16448
+ return import_react11.default.createElement(
16449
+ "button",
16450
+ {
16451
+ key: item.key,
16452
+ type: "button",
16453
+ onClick: () => onFilter(item.key),
16454
+ style: {
16455
+ display: "inline-flex",
16208
16456
  alignItems: "center",
16209
- gap: "8px",
16210
- padding: "6px 12px",
16457
+ gap: "5px",
16458
+ padding: "3px 10px",
16459
+ borderRadius: "12px",
16460
+ border: "none",
16461
+ background: isActive ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
16462
+ color: isActive ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)",
16211
16463
  cursor: "pointer",
16212
- borderRadius: "4px",
16213
16464
  fontSize: "13px",
16214
- color: "var(--dsw-fg)",
16215
- borderLeft: `3px solid ${SEVERITY_COLORS[finding.severity]}`,
16216
- marginBottom: "2px"
16217
- },
16218
- onClick: () => openDetail(finding)
16465
+ fontWeight: isActive ? 500 : 400,
16466
+ whiteSpace: "nowrap"
16467
+ }
16219
16468
  },
16220
- import_react10.default.createElement("span", {
16469
+ item.color !== void 0 && import_react11.default.createElement("span", {
16221
16470
  style: {
16222
16471
  width: "8px",
16223
16472
  height: "8px",
16224
16473
  borderRadius: "50%",
16225
- background: SEVERITY_COLORS[finding.severity],
16474
+ background: item.color,
16226
16475
  flexShrink: 0
16227
16476
  }
16228
16477
  }),
16229
- import_react10.default.createElement("span", { style: { fontWeight: 500 } }, finding.title),
16230
- finding.affectedAsset !== void 0 && import_react10.default.createElement("span", {
16231
- style: { fontSize: "11px", color: "var(--dsw-fg-muted)", marginLeft: "auto" }
16232
- }, finding.affectedAsset)
16233
- )
16234
- );
16235
- return import_react10.default.createElement(ExpandableSection, {
16236
- key: severity,
16237
- title: `${SEVERITY_LABELS[severity]} \xB7 ${items.length}`,
16238
- defaultOpen: severity === "critical" || severity === "high",
16239
- children: findingRows
16240
- });
16241
- });
16242
- const modalContent = selectedFinding !== null ? import_react10.default.createElement(
16243
- import_dsh_client_ui_primitives3.Modal,
16478
+ import_react11.default.createElement("span", null, item.label),
16479
+ import_react11.default.createElement("span", {
16480
+ style: { fontWeight: 600, color: isActive ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-tertiary)" }
16481
+ }, String(item.count))
16482
+ );
16483
+ })
16484
+ );
16485
+ }
16486
+ function FindingRow(props) {
16487
+ const { finding, isSelected, onClick } = props;
16488
+ const color = SEVERITY_COLORS[finding.severity];
16489
+ return import_react11.default.createElement(
16490
+ "div",
16244
16491
  {
16245
- open: true,
16246
- onClose: closeDetail,
16247
- title: selectedFinding.title,
16248
- contentClassName: "pentester-finding-detail"
16492
+ role: "button",
16493
+ tabIndex: 0,
16494
+ onClick,
16495
+ onKeyDown: (e) => {
16496
+ if (e.key === "Enter" || e.key === " ") {
16497
+ e.preventDefault();
16498
+ onClick();
16499
+ }
16500
+ },
16501
+ style: {
16502
+ display: "flex",
16503
+ flexDirection: "column",
16504
+ gap: "2px",
16505
+ padding: "8px 12px",
16506
+ cursor: "pointer",
16507
+ borderLeft: `3px solid ${isSelected ? color : "transparent"}`,
16508
+ background: isSelected ? "var(--dsw-alias-state-business-primary)" : "transparent",
16509
+ color: isSelected ? "var(--dsw-alias-state-business-primary-fg, #fff)" : "var(--dsw-alias-label-primary)",
16510
+ fontSize: "13px",
16511
+ lineHeight: "18px",
16512
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
16513
+ transition: "background 0.1s"
16514
+ },
16515
+ onMouseEnter: (e) => {
16516
+ if (!isSelected) e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover)";
16517
+ },
16518
+ onMouseLeave: (e) => {
16519
+ if (!isSelected) e.currentTarget.style.background = "transparent";
16520
+ }
16249
16521
  },
16250
- import_react10.default.createElement(
16522
+ // Row 1: title + severity
16523
+ import_react11.default.createElement(
16251
16524
  "div",
16252
- { style: { fontSize: "13px", lineHeight: "1.6" } },
16253
- import_react10.default.createElement("div", {
16525
+ {
16254
16526
  style: {
16255
- display: "inline-block",
16256
- padding: "2px 10px",
16257
- borderRadius: "10px",
16258
- background: SEVERITY_COLORS[selectedFinding.severity],
16259
- color: "#fff",
16260
- fontSize: "12px",
16527
+ display: "flex",
16528
+ alignItems: "center",
16529
+ gap: "8px",
16530
+ minWidth: 0
16531
+ }
16532
+ },
16533
+ import_react11.default.createElement("span", {
16534
+ style: {
16535
+ width: "7px",
16536
+ height: "7px",
16537
+ borderRadius: "50%",
16538
+ background: color,
16539
+ flexShrink: 0
16540
+ }
16541
+ }),
16542
+ import_react11.default.createElement("span", {
16543
+ style: {
16544
+ overflow: "hidden",
16545
+ textOverflow: "ellipsis",
16546
+ whiteSpace: "nowrap",
16547
+ fontWeight: 500,
16548
+ flex: 1,
16549
+ minWidth: 0
16550
+ }
16551
+ }, finding.title),
16552
+ import_react11.default.createElement("span", {
16553
+ style: {
16554
+ fontSize: "11px",
16261
16555
  fontWeight: 600,
16262
- marginBottom: "12px"
16556
+ color: isSelected ? "rgba(255,255,255,0.75)" : color,
16557
+ flexShrink: 0
16263
16558
  }
16264
- }, SEVERITY_LABELS[selectedFinding.severity]),
16265
- selectedFinding.affectedAsset !== void 0 && import_react10.default.createElement("div", {
16266
- style: { color: "var(--dsw-fg-muted)", marginBottom: "4px" }
16267
- }, "Affected: ", import_react10.default.createElement("code", null, selectedFinding.affectedAsset)),
16268
- import_react10.default.createElement("div", {
16269
- style: { color: "var(--dsw-fg-muted)", marginBottom: "12px", fontSize: "12px" }
16270
- }, `Source: ${selectedFinding.sourceStage} \xB7 ${selectedFinding.sourceDelegationId}`),
16271
- detailLoading && import_react10.default.createElement("div", {
16272
- style: { color: "var(--dsw-fg-muted)", padding: "16px 0" }
16273
- }, "Loading..."),
16274
- detailContent !== null && import_react10.default.createElement(import_dsh_client_ui_primitives3.MarkdownText, { text: detailContent })
16275
- )
16276
- ) : null;
16277
- return import_react10.default.createElement(
16559
+ }, SEVERITY_LABELS[finding.severity].toUpperCase())
16560
+ ),
16561
+ // Row 2: affected asset (if exists)
16562
+ finding.affectedAsset !== void 0 && import_react11.default.createElement("div", {
16563
+ style: {
16564
+ fontSize: "12px",
16565
+ color: isSelected ? "rgba(255,255,255,0.6)" : "var(--dsw-alias-label-tertiary)",
16566
+ overflow: "hidden",
16567
+ textOverflow: "ellipsis",
16568
+ whiteSpace: "nowrap",
16569
+ marginLeft: "15px"
16570
+ }
16571
+ }, finding.affectedAsset),
16572
+ // Row 3: source stage + delegation
16573
+ import_react11.default.createElement("div", {
16574
+ style: {
16575
+ fontSize: "11px",
16576
+ color: isSelected ? "rgba(255,255,255,0.5)" : "var(--dsw-alias-label-tertiary)",
16577
+ marginLeft: "15px",
16578
+ overflow: "hidden",
16579
+ textOverflow: "ellipsis",
16580
+ whiteSpace: "nowrap"
16581
+ }
16582
+ }, `${finding.sourceStage} \xB7 ${finding.sourceDelegationId}`)
16583
+ );
16584
+ }
16585
+ function FindingDetail(props) {
16586
+ const { finding, content, loading, error: error51, onNavigate } = props;
16587
+ const color = SEVERITY_COLORS[finding.severity];
16588
+ return import_react11.default.createElement(
16278
16589
  "div",
16279
- { style: { maxWidth: "640px" } },
16280
- ...severitySections,
16281
- modalContent
16590
+ {
16591
+ style: {
16592
+ flex: 1,
16593
+ minHeight: 0,
16594
+ display: "flex",
16595
+ flexDirection: "column",
16596
+ overflow: "hidden"
16597
+ }
16598
+ },
16599
+ // ── Metadata header (fixed) ─────────────────────────────────────────
16600
+ import_react11.default.createElement(
16601
+ "div",
16602
+ {
16603
+ style: {
16604
+ flex: "none",
16605
+ padding: "12px 16px",
16606
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
16607
+ }
16608
+ },
16609
+ // Title + severity + copy
16610
+ import_react11.default.createElement(
16611
+ "div",
16612
+ {
16613
+ style: {
16614
+ display: "flex",
16615
+ alignItems: "center",
16616
+ gap: "10px",
16617
+ marginBottom: "8px"
16618
+ }
16619
+ },
16620
+ import_react11.default.createElement("span", {
16621
+ style: {
16622
+ fontSize: "15px",
16623
+ fontWeight: 600,
16624
+ color: "var(--dsw-alias-label-primary)",
16625
+ overflow: "hidden",
16626
+ textOverflow: "ellipsis",
16627
+ whiteSpace: "nowrap",
16628
+ flex: 1,
16629
+ minWidth: 0
16630
+ }
16631
+ }, finding.title),
16632
+ content !== null && import_react11.default.createElement(CopyAction, { text: content }),
16633
+ import_react11.default.createElement("span", {
16634
+ style: {
16635
+ display: "inline-block",
16636
+ padding: "2px 8px",
16637
+ borderRadius: "10px",
16638
+ background: color,
16639
+ color: "#fff",
16640
+ fontSize: "11px",
16641
+ fontWeight: 600,
16642
+ flexShrink: 0
16643
+ }
16644
+ }, SEVERITY_LABELS[finding.severity].toUpperCase())
16645
+ ),
16646
+ // Affected asset
16647
+ finding.affectedAsset !== void 0 && import_react11.default.createElement(
16648
+ "div",
16649
+ {
16650
+ style: {
16651
+ fontSize: "13px",
16652
+ color: "var(--dsw-alias-label-secondary)",
16653
+ marginBottom: "6px",
16654
+ display: "flex",
16655
+ gap: "6px"
16656
+ }
16657
+ },
16658
+ import_react11.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary)", flexShrink: 0 } }, "Affected asset"),
16659
+ import_react11.default.createElement("code", {
16660
+ style: {
16661
+ fontSize: "12px",
16662
+ background: "var(--dsw-alias-bg-subtle)",
16663
+ padding: "1px 6px",
16664
+ borderRadius: "3px"
16665
+ }
16666
+ }, finding.affectedAsset)
16667
+ ),
16668
+ // Stage + Delegation metadata
16669
+ import_react11.default.createElement(
16670
+ "div",
16671
+ {
16672
+ style: {
16673
+ display: "flex",
16674
+ gap: "20px",
16675
+ fontSize: "12px",
16676
+ color: "var(--dsw-alias-label-tertiary)"
16677
+ }
16678
+ },
16679
+ import_react11.default.createElement(
16680
+ "div",
16681
+ null,
16682
+ import_react11.default.createElement("span", { style: { fontWeight: 500, color: "var(--dsw-alias-label-secondary)" } }, "Stage"),
16683
+ " ",
16684
+ import_react11.default.createElement("span", null, finding.sourceStage)
16685
+ ),
16686
+ import_react11.default.createElement(
16687
+ "div",
16688
+ null,
16689
+ import_react11.default.createElement("span", { style: { fontWeight: 500, color: "var(--dsw-alias-label-secondary)" } }, "Delegation"),
16690
+ " ",
16691
+ import_react11.default.createElement("span", null, finding.sourceDelegationId)
16692
+ )
16693
+ )
16694
+ ),
16695
+ // ── DetailBody (scrollable) ────────────────────────────────────────
16696
+ import_react11.default.createElement(
16697
+ "div",
16698
+ {
16699
+ style: {
16700
+ flex: 1,
16701
+ minHeight: 0,
16702
+ overflowY: "auto",
16703
+ overflowX: "auto",
16704
+ overscrollBehaviorY: "contain",
16705
+ scrollbarGutter: "stable",
16706
+ padding: "16px",
16707
+ fontSize: "13px",
16708
+ lineHeight: "1.6",
16709
+ color: "var(--dsw-alias-label-primary)"
16710
+ }
16711
+ },
16712
+ loading && import_react11.default.createElement("div", {
16713
+ style: { color: "var(--dsw-alias-label-tertiary)", padding: "16px 0" }
16714
+ }, "Loading finding..."),
16715
+ error51 && import_react11.default.createElement("div", {
16716
+ style: { color: "var(--dsw-status-error)", padding: "16px 0" }
16717
+ }, "Unable to load finding detail"),
16718
+ content !== null && import_react11.default.createElement(import_dsh_client_ui_primitives4.MarkdownText, { text: content })
16719
+ ),
16720
+ // ── Footer actions (fixed) ──────────────────────────────────────────
16721
+ import_react11.default.createElement(
16722
+ "div",
16723
+ {
16724
+ style: {
16725
+ flex: "none",
16726
+ padding: "8px 16px",
16727
+ borderTop: "1px solid var(--dsw-alias-border-l2)",
16728
+ display: "flex",
16729
+ gap: "8px"
16730
+ }
16731
+ },
16732
+ import_react11.default.createElement(ActionButton, {
16733
+ label: "View Stage",
16734
+ onClick: () => onNavigate("stages", { stage: finding.sourceStage })
16735
+ }),
16736
+ import_react11.default.createElement(ActionButton, {
16737
+ label: "View in Output",
16738
+ onClick: () => onNavigate("output", { output: `findings/${finding.detailFile}` })
16739
+ })
16740
+ )
16282
16741
  );
16283
16742
  }
16743
+ function ActionButton(props) {
16744
+ return import_react11.default.createElement("button", {
16745
+ type: "button",
16746
+ onClick: props.onClick,
16747
+ style: {
16748
+ padding: "4px 12px",
16749
+ border: "1px solid var(--dsw-alias-border-l2)",
16750
+ borderRadius: "4px",
16751
+ background: "transparent",
16752
+ color: "var(--dsw-alias-label-secondary)",
16753
+ cursor: "pointer",
16754
+ fontSize: "12px",
16755
+ lineHeight: "20px"
16756
+ },
16757
+ onMouseEnter: (e) => {
16758
+ e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover)";
16759
+ },
16760
+ onMouseLeave: (e) => {
16761
+ e.currentTarget.style.background = "transparent";
16762
+ }
16763
+ }, props.label);
16764
+ }
16284
16765
 
16285
16766
  // src/ui/client/pentester/TraceView.tsx
16286
- var import_react15 = __toESM(require("react"), 1);
16767
+ var import_react16 = __toESM(require("react"), 1);
16287
16768
 
16288
16769
  // src/ui/client/pentester/trace/TraceTimeline.tsx
16289
- var import_react14 = __toESM(require("react"), 1);
16770
+ var import_react15 = __toESM(require("react"), 1);
16290
16771
 
16291
16772
  // src/ui/client/pentester/trace/types.ts
16292
16773
  var STAGE_NODE_H = 72;
@@ -16690,8 +17171,8 @@ function buildScaledTraceLayout(baseLayout, scale) {
16690
17171
  }
16691
17172
 
16692
17173
  // src/ui/client/pentester/trace/TraceNode.tsx
16693
- var import_react11 = __toESM(require("react"), 1);
16694
- var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
17174
+ var import_react12 = __toESM(require("react"), 1);
17175
+ var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
16695
17176
  var CARD = {
16696
17177
  position: "absolute",
16697
17178
  borderRadius: "7px",
@@ -16704,10 +17185,10 @@ var CARD = {
16704
17185
  cursor: "pointer",
16705
17186
  pointerEvents: "auto"
16706
17187
  };
16707
- var StartNode = (0, import_react11.forwardRef)(
17188
+ var StartNode = (0, import_react12.forwardRef)(
16708
17189
  function StartNode2(props, ref) {
16709
17190
  const { section, x } = props;
16710
- return import_react11.default.createElement(
17191
+ return import_react12.default.createElement(
16711
17192
  "div",
16712
17193
  {
16713
17194
  ref,
@@ -16735,7 +17216,7 @@ var StartNode = (0, import_react11.forwardRef)(
16735
17216
  pointerEvents: "auto"
16736
17217
  }
16737
17218
  },
16738
- import_react11.default.createElement("div", {
17219
+ import_react12.default.createElement("div", {
16739
17220
  style: {
16740
17221
  width: "6px",
16741
17222
  height: "6px",
@@ -16748,13 +17229,13 @@ var StartNode = (0, import_react11.forwardRef)(
16748
17229
  );
16749
17230
  }
16750
17231
  );
16751
- var StageNode = (0, import_react11.forwardRef)(
17232
+ var StageNode = (0, import_react12.forwardRef)(
16752
17233
  function StageNode2(props, ref) {
16753
17234
  const { section, x, onClick } = props;
16754
17235
  const status = section.status;
16755
17236
  const borderColor = status === "completed" ? "var(--dsw-status-done, #22c55e)" : status === "active" ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-border-l2)";
16756
17237
  const dotState = status === "completed" ? "done" : status === "active" ? "ongoing" : "ongoing";
16757
- return import_react11.default.createElement(
17238
+ return import_react12.default.createElement(
16758
17239
  "div",
16759
17240
  {
16760
17241
  ref,
@@ -16782,26 +17263,26 @@ var StageNode = (0, import_react11.forwardRef)(
16782
17263
  gap: "10px"
16783
17264
  }
16784
17265
  },
16785
- import_react11.default.createElement(import_dsh_client_ui_primitives4.StateDot, { state: dotState, size: 8 }),
16786
- import_react11.default.createElement(
17266
+ import_react12.default.createElement(import_dsh_client_ui_primitives5.StateDot, { state: dotState, size: 8 }),
17267
+ import_react12.default.createElement(
16787
17268
  "div",
16788
17269
  { style: { minWidth: 0 } },
16789
- import_react11.default.createElement("div", {
17270
+ import_react12.default.createElement("div", {
16790
17271
  style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)", fontSize: "14px", lineHeight: "20px" }
16791
17272
  }, section.label),
16792
- import_react11.default.createElement("div", {
17273
+ import_react12.default.createElement("div", {
16793
17274
  style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary)", marginTop: "2px" }
16794
17275
  }, section.sublabel)
16795
17276
  )
16796
17277
  );
16797
17278
  }
16798
17279
  );
16799
- var AgentNode = (0, import_react11.forwardRef)(
17280
+ var AgentNode = (0, import_react12.forwardRef)(
16800
17281
  function AgentNode2(props, ref) {
16801
17282
  const { branch, x, liveActivity, onClick } = props;
16802
17283
  const dotState = branch.status === "failed" ? "error" : branch.status === "closed" ? "done" : liveActivity === "running" ? "ongoing" : liveActivity === "inactive" ? "done" : branch.status === "starting" ? "ongoing" : "done";
16803
17284
  const borderColor = dotState === "error" ? "var(--dsw-status-error, #ef4444)" : dotState === "ongoing" ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-border-l2)";
16804
- return import_react11.default.createElement(
17285
+ return import_react12.default.createElement(
16805
17286
  "div",
16806
17287
  {
16807
17288
  ref,
@@ -16829,14 +17310,14 @@ var AgentNode = (0, import_react11.forwardRef)(
16829
17310
  gap: "8px"
16830
17311
  }
16831
17312
  },
16832
- import_react11.default.createElement(import_dsh_client_ui_primitives4.StateDot, { state: dotState, size: 8 }),
16833
- import_react11.default.createElement(
17313
+ import_react12.default.createElement(import_dsh_client_ui_primitives5.StateDot, { state: dotState, size: 8 }),
17314
+ import_react12.default.createElement(
16834
17315
  "div",
16835
17316
  { style: { minWidth: 0 } },
16836
- import_react11.default.createElement("div", {
17317
+ import_react12.default.createElement("div", {
16837
17318
  style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)", fontSize: "13px", lineHeight: "18px" }
16838
17319
  }, branch.label),
16839
- import_react11.default.createElement("div", {
17320
+ import_react12.default.createElement("div", {
16840
17321
  style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", marginTop: "2px" }
16841
17322
  }, branch.status === "starting" ? "Agent starting" : branch.status === "active" ? "Agent active" : "Agent completed")
16842
17323
  )
@@ -16851,11 +17332,11 @@ var DELIVERY_ICON_COLORS = {
16851
17332
  finding: "var(--dsw-status-error, #ef4444)",
16852
17333
  asset: "#3b82f6"
16853
17334
  };
16854
- var DeliveryNode = (0, import_react11.forwardRef)(
17335
+ var DeliveryNode = (0, import_react12.forwardRef)(
16855
17336
  function DeliveryNode2(props, ref) {
16856
17337
  const { group, x, y, onClick } = props;
16857
17338
  const iconColor = DELIVERY_ICON_COLORS[group.kind] ?? "var(--dsw-alias-label-tertiary)";
16858
- return import_react11.default.createElement(
17339
+ return import_react12.default.createElement(
16859
17340
  "div",
16860
17341
  {
16861
17342
  ref,
@@ -16887,7 +17368,7 @@ var DeliveryNode = (0, import_react11.forwardRef)(
16887
17368
  textAlign: "center"
16888
17369
  }
16889
17370
  },
16890
- import_react11.default.createElement("div", {
17371
+ import_react12.default.createElement("div", {
16891
17372
  style: {
16892
17373
  width: "8px",
16893
17374
  height: "8px",
@@ -16896,10 +17377,10 @@ var DeliveryNode = (0, import_react11.forwardRef)(
16896
17377
  flexShrink: 0
16897
17378
  }
16898
17379
  }),
16899
- import_react11.default.createElement("div", {
17380
+ import_react12.default.createElement("div", {
16900
17381
  style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)", fontSize: "12px", lineHeight: "16px" }
16901
17382
  }, group.label),
16902
- import_react11.default.createElement("div", {
17383
+ import_react12.default.createElement("div", {
16903
17384
  style: { fontSize: "10px", color: "var(--dsw-alias-label-tertiary)" }
16904
17385
  }, `${group.count} ${group.count === 1 ? "file" : "files"}`)
16905
17386
  );
@@ -16907,7 +17388,7 @@ var DeliveryNode = (0, import_react11.forwardRef)(
16907
17388
  );
16908
17389
 
16909
17390
  // src/ui/client/pentester/trace/TraceConnectors.tsx
16910
- var import_react12 = __toESM(require("react"), 1);
17391
+ var import_react13 = __toESM(require("react"), 1);
16911
17392
  function roundedOrthogonal(sx, sy, tx, ty, r = 6) {
16912
17393
  if (Math.abs(sx - tx) < 2) {
16913
17394
  return `M ${sx} ${sy} L ${tx} ${ty}`;
@@ -16932,7 +17413,7 @@ function branchJunction(cx, sy, targets) {
16932
17413
  }
16933
17414
  function TraceConnectors(props) {
16934
17415
  const { layout } = props;
16935
- const paths = (0, import_react12.useMemo)(() => {
17416
+ const paths = (0, import_react13.useMemo)(() => {
16936
17417
  const result = [];
16937
17418
  const cx = layout.spineX;
16938
17419
  const solid = { stroke: "var(--dsw-alias-border-l2)", strokeWidth: 1.25 };
@@ -17013,7 +17494,7 @@ function TraceConnectors(props) {
17013
17494
  }
17014
17495
  return result;
17015
17496
  }, [layout]);
17016
- return import_react12.default.createElement(
17497
+ return import_react13.default.createElement(
17017
17498
  "svg",
17018
17499
  {
17019
17500
  width: layout.worldHalfWidth * 2,
@@ -17026,7 +17507,7 @@ function TraceConnectors(props) {
17026
17507
  }
17027
17508
  },
17028
17509
  ...paths.map(
17029
- (p, i) => import_react12.default.createElement("path", {
17510
+ (p, i) => import_react13.default.createElement("path", {
17030
17511
  key: i,
17031
17512
  d: p.d,
17032
17513
  fill: "none",
@@ -17041,7 +17522,7 @@ function TraceConnectors(props) {
17041
17522
  }
17042
17523
 
17043
17524
  // src/ui/client/pentester/trace/TraceControls.tsx
17044
- var import_react13 = __toESM(require("react"), 1);
17525
+ var import_react14 = __toESM(require("react"), 1);
17045
17526
  var BTN = {
17046
17527
  height: "32px",
17047
17528
  display: "flex",
@@ -17060,33 +17541,33 @@ var BTN = {
17060
17541
  function TraceControls(props) {
17061
17542
  const { scale, onZoomIn, onZoomOut, onReset, onFit } = props;
17062
17543
  const pct = Math.round(scale * 100);
17063
- return import_react13.default.createElement(
17544
+ return import_react14.default.createElement(
17064
17545
  "div",
17065
17546
  {
17066
17547
  style: { display: "flex", alignItems: "center", gap: "4px" }
17067
17548
  },
17068
- import_react13.default.createElement("button", {
17549
+ import_react14.default.createElement("button", {
17069
17550
  type: "button",
17070
17551
  "aria-label": "Zoom out",
17071
17552
  title: "Zoom out",
17072
17553
  onClick: onZoomOut,
17073
17554
  style: { ...BTN, width: "32px", padding: 0 }
17074
17555
  }, "\u2212"),
17075
- import_react13.default.createElement("button", {
17556
+ import_react14.default.createElement("button", {
17076
17557
  type: "button",
17077
17558
  "aria-label": "Reset zoom",
17078
17559
  title: "Reset to 100%",
17079
17560
  onClick: onReset,
17080
17561
  style: { ...BTN, fontWeight: 500, fontSize: "11px", color: "var(--dsw-alias-label-primary)" }
17081
17562
  }, `${pct}%`),
17082
- import_react13.default.createElement("button", {
17563
+ import_react14.default.createElement("button", {
17083
17564
  type: "button",
17084
17565
  "aria-label": "Zoom in",
17085
17566
  title: "Zoom in",
17086
17567
  onClick: onZoomIn,
17087
17568
  style: { ...BTN, width: "32px", padding: 0 }
17088
17569
  }, "+"),
17089
- import_react13.default.createElement("button", {
17570
+ import_react14.default.createElement("button", {
17090
17571
  type: "button",
17091
17572
  "aria-label": "Fit to width",
17092
17573
  title: "Fit topology width",
@@ -17115,12 +17596,12 @@ function checkSpineInvariant(graphEl, scale) {
17115
17596
  }
17116
17597
  function TraceTimeline(props) {
17117
17598
  const { snapshot, liveActivity, onNavigate, onOpenSession } = props;
17118
- const baseLayout = (0, import_react14.useMemo)(() => buildTraceLayout(snapshot), [snapshot]);
17119
- const [scale, setScale] = (0, import_react14.useState)(DEFAULT_SCALE);
17120
- const graphRef = (0, import_react14.useRef)(null);
17121
- const graphVpRef = (0, import_react14.useRef)(null);
17122
- const autoFitDoneRef = (0, import_react14.useRef)(false);
17123
- const scaledLayout = (0, import_react14.useMemo)(
17599
+ const baseLayout = (0, import_react15.useMemo)(() => buildTraceLayout(snapshot), [snapshot]);
17600
+ const [scale, setScale] = (0, import_react15.useState)(DEFAULT_SCALE);
17601
+ const graphRef = (0, import_react15.useRef)(null);
17602
+ const graphVpRef = (0, import_react15.useRef)(null);
17603
+ const autoFitDoneRef = (0, import_react15.useRef)(false);
17604
+ const scaledLayout = (0, import_react15.useMemo)(
17124
17605
  () => buildScaledTraceLayout(baseLayout, scale),
17125
17606
  [baseLayout, scale]
17126
17607
  );
@@ -17130,7 +17611,7 @@ function TraceTimeline(props) {
17130
17611
  const spineX = scaledLayout.spineX;
17131
17612
  const worldW = worldHalfWidth * 2;
17132
17613
  const worldH = scaledLayout.totalHeight;
17133
- const doAutoFit = (0, import_react14.useCallback)(() => {
17614
+ const doAutoFit = (0, import_react15.useCallback)(() => {
17134
17615
  const vp = graphVpRef.current;
17135
17616
  if (vp === null) return DEFAULT_SCALE;
17136
17617
  const availableHalf = vp.clientWidth / 2 - GRAPH_PADDING;
@@ -17138,13 +17619,13 @@ function TraceTimeline(props) {
17138
17619
  const fitScale = availableHalf / requiredHalf;
17139
17620
  return Math.min(DEFAULT_SCALE, Math.max(MIN_SCALE, fitScale));
17140
17621
  }, [baseLayout.worldHalfWidth]);
17141
- (0, import_react14.useEffect)(() => {
17622
+ (0, import_react15.useEffect)(() => {
17142
17623
  if (autoFitDoneRef.current) return;
17143
17624
  const fitScale = doAutoFit();
17144
17625
  if (fitScale !== DEFAULT_SCALE) setScale(fitScale);
17145
17626
  autoFitDoneRef.current = true;
17146
17627
  }, [doAutoFit]);
17147
- const zoomTo = (0, import_react14.useCallback)((newScale) => {
17628
+ const zoomTo = (0, import_react15.useCallback)((newScale) => {
17148
17629
  const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale));
17149
17630
  const owner = getScrollOwner();
17150
17631
  const oldScale = scale;
@@ -17157,26 +17638,26 @@ function TraceTimeline(props) {
17157
17638
  owner.scrollTop = Math.max(0, nextScrollTop);
17158
17639
  });
17159
17640
  }, [scale]);
17160
- const zoomIn = (0, import_react14.useCallback)(() => zoomTo(scale * 1.15), [zoomTo, scale]);
17161
- const zoomOut = (0, import_react14.useCallback)(() => zoomTo(scale / 1.15), [zoomTo, scale]);
17162
- const resetZoom = (0, import_react14.useCallback)(() => zoomTo(DEFAULT_SCALE), [zoomTo]);
17163
- const fitWidth = (0, import_react14.useCallback)(() => zoomTo(doAutoFit()), [zoomTo, doAutoFit]);
17164
- const handleWheel = (0, import_react14.useCallback)((e) => {
17641
+ const zoomIn = (0, import_react15.useCallback)(() => zoomTo(scale * 1.15), [zoomTo, scale]);
17642
+ const zoomOut = (0, import_react15.useCallback)(() => zoomTo(scale / 1.15), [zoomTo, scale]);
17643
+ const resetZoom = (0, import_react15.useCallback)(() => zoomTo(DEFAULT_SCALE), [zoomTo]);
17644
+ const fitWidth = (0, import_react15.useCallback)(() => zoomTo(doAutoFit()), [zoomTo, doAutoFit]);
17645
+ const handleWheel = (0, import_react15.useCallback)((e) => {
17165
17646
  if (!e.ctrlKey && !e.metaKey) return;
17166
17647
  e.preventDefault();
17167
17648
  const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12;
17168
17649
  zoomTo(scale * factor);
17169
17650
  }, [zoomTo, scale]);
17170
- (0, import_react14.useEffect)(() => {
17651
+ (0, import_react15.useEffect)(() => {
17171
17652
  checkSpineInvariant(graphRef.current, scale);
17172
17653
  }, [scale, adjustedSections]);
17173
- const handleStageClick = (0, import_react14.useCallback)((stageId) => {
17654
+ const handleStageClick = (0, import_react15.useCallback)((stageId) => {
17174
17655
  onNavigate("stages", { stage: stageId });
17175
17656
  }, [onNavigate]);
17176
- const handleAgentClick = (0, import_react14.useCallback)((sessionId) => {
17657
+ const handleAgentClick = (0, import_react15.useCallback)((sessionId) => {
17177
17658
  if (sessionId !== void 0) onOpenSession(sessionId);
17178
17659
  }, [onOpenSession]);
17179
- const handleDeliveryClick = (0, import_react14.useCallback)((group) => {
17660
+ const handleDeliveryClick = (0, import_react15.useCallback)((group) => {
17180
17661
  if (group.navTab === "findings") {
17181
17662
  onNavigate("findings", { finding: group.navPath });
17182
17663
  } else {
@@ -17185,7 +17666,7 @@ function TraceTimeline(props) {
17185
17666
  }, [onNavigate]);
17186
17667
  const gridCols = `${TIME_PANEL_W}px minmax(0, 1fr)`;
17187
17668
  const timelineEventGrid = `${TIMELINE_DOT_W}px ${TIMELINE_TIME_W}px minmax(0, 1fr)`;
17188
- return import_react14.default.createElement(
17669
+ return import_react15.default.createElement(
17189
17670
  "div",
17190
17671
  {
17191
17672
  style: {
@@ -17198,7 +17679,7 @@ function TraceTimeline(props) {
17198
17679
  }
17199
17680
  },
17200
17681
  // ── TraceHeader ─────────────────────────────────────────────────────────
17201
- import_react14.default.createElement(
17682
+ import_react15.default.createElement(
17202
17683
  "div",
17203
17684
  {
17204
17685
  "data-testid": "trace-header",
@@ -17210,16 +17691,16 @@ function TraceTimeline(props) {
17210
17691
  background: "var(--dsw-alias-bg-base)"
17211
17692
  }
17212
17693
  },
17213
- import_react14.default.createElement(
17694
+ import_react15.default.createElement(
17214
17695
  "div",
17215
17696
  {
17216
17697
  style: { padding: "12px 18px", borderRight: "1px solid var(--dsw-alias-border-l1)" }
17217
17698
  },
17218
- import_react14.default.createElement("div", {
17699
+ import_react15.default.createElement("div", {
17219
17700
  style: { fontSize: "13px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
17220
17701
  }, "Timeline")
17221
17702
  ),
17222
- import_react14.default.createElement(
17703
+ import_react15.default.createElement(
17223
17704
  "div",
17224
17705
  {
17225
17706
  style: {
@@ -17229,10 +17710,10 @@ function TraceTimeline(props) {
17229
17710
  justifyContent: "space-between"
17230
17711
  }
17231
17712
  },
17232
- import_react14.default.createElement("div", {
17713
+ import_react15.default.createElement("div", {
17233
17714
  style: { fontSize: "13px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
17234
17715
  }, "Topology"),
17235
- import_react14.default.createElement(TraceControls, {
17716
+ import_react15.default.createElement(TraceControls, {
17236
17717
  scale,
17237
17718
  onZoomIn: zoomIn,
17238
17719
  onZoomOut: zoomOut,
@@ -17242,7 +17723,7 @@ function TraceTimeline(props) {
17242
17723
  )
17243
17724
  ),
17244
17725
  // ── Content (no scroll — flows into Harness scroll owner) ───────────────
17245
- import_react14.default.createElement(
17726
+ import_react15.default.createElement(
17246
17727
  "div",
17247
17728
  {
17248
17729
  onWheel: handleWheel,
@@ -17252,7 +17733,7 @@ function TraceTimeline(props) {
17252
17733
  }
17253
17734
  },
17254
17735
  // ── Timeline rail ────────────────────────────────────────────────────
17255
- import_react14.default.createElement(
17736
+ import_react15.default.createElement(
17256
17737
  "div",
17257
17738
  {
17258
17739
  style: {
@@ -17263,7 +17744,7 @@ function TraceTimeline(props) {
17263
17744
  }
17264
17745
  },
17265
17746
  // Vertical timeline spine line
17266
- import_react14.default.createElement("div", {
17747
+ import_react15.default.createElement("div", {
17267
17748
  "aria-hidden": true,
17268
17749
  style: {
17269
17750
  position: "absolute",
@@ -17278,7 +17759,7 @@ function TraceTimeline(props) {
17278
17759
  ...scaledBands.map((band) => {
17279
17760
  const isStart = band.bandId === "band:start";
17280
17761
  if (band.timelineItems.length === 0) return null;
17281
- return import_react14.default.createElement(
17762
+ return import_react15.default.createElement(
17282
17763
  "div",
17283
17764
  {
17284
17765
  key: band.bandId,
@@ -17295,7 +17776,7 @@ function TraceTimeline(props) {
17295
17776
  },
17296
17777
  // Start band
17297
17778
  isStart && band.timelineItems.map(
17298
- (item, ii) => import_react14.default.createElement(
17779
+ (item, ii) => import_react15.default.createElement(
17299
17780
  "div",
17300
17781
  {
17301
17782
  key: ii,
@@ -17307,12 +17788,12 @@ function TraceTimeline(props) {
17307
17788
  minHeight: `${TIMELINE_ROW_H}px`
17308
17789
  }
17309
17790
  },
17310
- import_react14.default.createElement(
17791
+ import_react15.default.createElement(
17311
17792
  "div",
17312
17793
  {
17313
17794
  style: { display: "flex", justifyContent: "center", paddingTop: "4px" }
17314
17795
  },
17315
- import_react14.default.createElement("div", {
17796
+ import_react15.default.createElement("div", {
17316
17797
  style: {
17317
17798
  width: "6px",
17318
17799
  height: "6px",
@@ -17321,7 +17802,7 @@ function TraceTimeline(props) {
17321
17802
  }
17322
17803
  })
17323
17804
  ),
17324
- import_react14.default.createElement("span", {
17805
+ import_react15.default.createElement("span", {
17325
17806
  style: {
17326
17807
  fontSize: "11px",
17327
17808
  fontFamily: "monospace",
@@ -17330,13 +17811,13 @@ function TraceTimeline(props) {
17330
17811
  paddingTop: "2px"
17331
17812
  }
17332
17813
  }, item.timeLabel),
17333
- import_react14.default.createElement(
17814
+ import_react15.default.createElement(
17334
17815
  "div",
17335
17816
  { style: { minWidth: 0, paddingTop: "2px" } },
17336
- import_react14.default.createElement("div", {
17817
+ import_react15.default.createElement("div", {
17337
17818
  style: { fontSize: "13px", fontWeight: 500, color: "var(--dsw-alias-label-primary)", lineHeight: "18px" }
17338
17819
  }, item.title),
17339
- item.subtitle !== "" && import_react14.default.createElement("div", {
17820
+ item.subtitle !== "" && import_react15.default.createElement("div", {
17340
17821
  style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", lineHeight: "16px" }
17341
17822
  }, item.subtitle)
17342
17823
  )
@@ -17344,7 +17825,7 @@ function TraceTimeline(props) {
17344
17825
  ),
17345
17826
  // Regular stage/agent items
17346
17827
  !isStart && band.timelineItems.map(
17347
- (item, ii) => import_react14.default.createElement(
17828
+ (item, ii) => import_react15.default.createElement(
17348
17829
  "div",
17349
17830
  {
17350
17831
  key: ii,
@@ -17356,12 +17837,12 @@ function TraceTimeline(props) {
17356
17837
  minHeight: `${TIMELINE_ROW_H}px`
17357
17838
  }
17358
17839
  },
17359
- import_react14.default.createElement(
17840
+ import_react15.default.createElement(
17360
17841
  "div",
17361
17842
  {
17362
17843
  style: { display: "flex", justifyContent: "center", paddingTop: "4px" }
17363
17844
  },
17364
- import_react14.default.createElement("div", {
17845
+ import_react15.default.createElement("div", {
17365
17846
  style: {
17366
17847
  width: "8px",
17367
17848
  height: "8px",
@@ -17370,7 +17851,7 @@ function TraceTimeline(props) {
17370
17851
  }
17371
17852
  })
17372
17853
  ),
17373
- import_react14.default.createElement("span", {
17854
+ import_react15.default.createElement("span", {
17374
17855
  style: {
17375
17856
  fontSize: "11px",
17376
17857
  fontFamily: "monospace",
@@ -17379,13 +17860,13 @@ function TraceTimeline(props) {
17379
17860
  paddingTop: "2px"
17380
17861
  }
17381
17862
  }, item.timeLabel),
17382
- import_react14.default.createElement(
17863
+ import_react15.default.createElement(
17383
17864
  "div",
17384
17865
  { style: { minWidth: 0, paddingTop: "2px" } },
17385
- import_react14.default.createElement("div", {
17866
+ import_react15.default.createElement("div", {
17386
17867
  style: { fontSize: "13px", fontWeight: 500, color: "var(--dsw-alias-label-primary)", lineHeight: "18px" }
17387
17868
  }, item.title),
17388
- item.subtitle !== "" && import_react14.default.createElement("div", {
17869
+ item.subtitle !== "" && import_react15.default.createElement("div", {
17389
17870
  style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", lineHeight: "16px" }
17390
17871
  }, item.subtitle)
17391
17872
  )
@@ -17395,7 +17876,7 @@ function TraceTimeline(props) {
17395
17876
  })
17396
17877
  ),
17397
17878
  // ── Graph viewport ────────────────────────────────────────────────────
17398
- import_react14.default.createElement(
17879
+ import_react15.default.createElement(
17399
17880
  "div",
17400
17881
  {
17401
17882
  ref: graphVpRef,
@@ -17407,7 +17888,7 @@ function TraceTimeline(props) {
17407
17888
  }
17408
17889
  },
17409
17890
  // SpineAnchor: CSS left:50%, always at viewport center
17410
- import_react14.default.createElement(
17891
+ import_react15.default.createElement(
17411
17892
  "div",
17412
17893
  {
17413
17894
  style: {
@@ -17420,7 +17901,7 @@ function TraceTimeline(props) {
17420
17901
  }
17421
17902
  },
17422
17903
  // TopologyWorld: symmetric around spine
17423
- import_react14.default.createElement(
17904
+ import_react15.default.createElement(
17424
17905
  "div",
17425
17906
  {
17426
17907
  ref: graphRef,
@@ -17434,24 +17915,24 @@ function TraceTimeline(props) {
17434
17915
  transformOrigin: `${spineX}px 0px`
17435
17916
  }
17436
17917
  },
17437
- import_react14.default.createElement(TraceConnectors, {
17918
+ import_react15.default.createElement(TraceConnectors, {
17438
17919
  layout: { ...baseLayout, sections: adjustedSections, worldHeight: worldH, spineX },
17439
17920
  rects: /* @__PURE__ */ new Map()
17440
17921
  }),
17441
- import_react14.default.createElement(
17922
+ import_react15.default.createElement(
17442
17923
  "div",
17443
17924
  {
17444
17925
  style: { position: "absolute", inset: 0, zIndex: 1 }
17445
17926
  },
17446
17927
  ...adjustedSections.filter((s) => s.stageId === "start").map(
17447
- (section) => import_react14.default.createElement(StartNode, {
17928
+ (section) => import_react15.default.createElement(StartNode, {
17448
17929
  key: "stage:start",
17449
17930
  section,
17450
17931
  x: spineX
17451
17932
  })
17452
17933
  ),
17453
17934
  ...adjustedSections.filter((s) => s.stageId !== "start").map(
17454
- (section) => import_react14.default.createElement(StageNode, {
17935
+ (section) => import_react15.default.createElement(StageNode, {
17455
17936
  key: `stage:${section.stageId}`,
17456
17937
  section,
17457
17938
  x: spineX,
@@ -17460,7 +17941,7 @@ function TraceTimeline(props) {
17460
17941
  ),
17461
17942
  ...adjustedSections.flatMap(
17462
17943
  (section) => section.agentBranches.map(
17463
- (branch) => import_react14.default.createElement(AgentNode, {
17944
+ (branch) => import_react15.default.createElement(AgentNode, {
17464
17945
  key: `agent:${branch.delegationId}`,
17465
17946
  branch,
17466
17947
  x: spineX + branch.x,
@@ -17475,7 +17956,7 @@ function TraceTimeline(props) {
17475
17956
  (row3, ri) => row3.map((dx, gi) => {
17476
17957
  const group = branch.deliveryGroups[ri * 3 + gi];
17477
17958
  if (group === void 0) return null;
17478
- return import_react14.default.createElement(DeliveryNode, {
17959
+ return import_react15.default.createElement(DeliveryNode, {
17479
17960
  key: `delivery:${branch.delegationId}:${group.kind}`,
17480
17961
  group,
17481
17962
  x: spineX + dx,
@@ -17498,11 +17979,11 @@ function TraceTimeline(props) {
17498
17979
  function TraceView(props) {
17499
17980
  const { snapshot, liveActivity, onNavigate, onOpenSession, activeTargetId } = props;
17500
17981
  if (snapshot === null || snapshot.run === null) {
17501
- return import_react15.default.createElement("div", {
17982
+ return import_react16.default.createElement("div", {
17502
17983
  style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17503
17984
  }, "No execution trace yet");
17504
17985
  }
17505
- return import_react15.default.createElement(TraceTimeline, {
17986
+ return import_react16.default.createElement(TraceTimeline, {
17506
17987
  snapshot,
17507
17988
  liveActivity,
17508
17989
  onNavigate,
@@ -17512,21 +17993,21 @@ function TraceView(props) {
17512
17993
  }
17513
17994
 
17514
17995
  // src/ui/client/pentester/OutputView.tsx
17515
- var import_react17 = __toESM(require("react"), 1);
17516
- var import_dsh_client_ui_primitives6 = require("@deepseek-ai/dsh-client-ui-primitives");
17996
+ var import_react18 = __toESM(require("react"), 1);
17997
+ var import_dsh_client_ui_primitives7 = require("@deepseek-ai/dsh-client-ui-primitives");
17517
17998
 
17518
17999
  // src/ui/client/pentester/FolderTreeRow.tsx
17519
- var import_react16 = __toESM(require("react"), 1);
17520
- var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
18000
+ var import_react17 = __toESM(require("react"), 1);
18001
+ var import_dsh_client_ui_primitives6 = require("@deepseek-ai/dsh-client-ui-primitives");
17521
18002
  function FolderTreeRow(props) {
17522
18003
  const { name, depth, defaultOpen = true, children, containsSelected = false } = props;
17523
- const [open, setOpen] = (0, import_react16.useState)(defaultOpen);
17524
- const hasChildren = children !== void 0 && import_react16.default.Children.count(children) > 0;
18004
+ const [open, setOpen] = (0, import_react17.useState)(defaultOpen);
18005
+ const hasChildren = children !== void 0 && import_react17.default.Children.count(children) > 0;
17525
18006
  const folderColor = containsSelected ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-label-secondary)";
17526
- return import_react16.default.createElement(
18007
+ return import_react17.default.createElement(
17527
18008
  "div",
17528
18009
  null,
17529
- import_react16.default.createElement(
18010
+ import_react17.default.createElement(
17530
18011
  "div",
17531
18012
  {
17532
18013
  role: "button",
@@ -17562,19 +18043,19 @@ function FolderTreeRow(props) {
17562
18043
  }
17563
18044
  },
17564
18045
  // Chevron
17565
- hasChildren ? import_react16.default.createElement(
18046
+ hasChildren ? import_react17.default.createElement(
17566
18047
  "span",
17567
18048
  { style: { flexShrink: 0, color: "var(--dsw-alias-label-tertiary)" } },
17568
- import_react16.default.createElement(open ? import_dsh_client_ui_primitives5.IconChevronDownOutline14 : import_dsh_client_ui_primitives5.IconChevronRightOutline14, { size: 14 })
17569
- ) : import_react16.default.createElement("span", { style: { width: "14px", flexShrink: 0 } }),
18049
+ import_react17.default.createElement(open ? import_dsh_client_ui_primitives6.IconChevronDownOutline14 : import_dsh_client_ui_primitives6.IconChevronRightOutline14, { size: 14 })
18050
+ ) : import_react17.default.createElement("span", { style: { width: "14px", flexShrink: 0 } }),
17570
18051
  // Folder icon — color reflects selected ancestry
17571
- import_react16.default.createElement(
18052
+ import_react17.default.createElement(
17572
18053
  "span",
17573
18054
  { style: { flexShrink: 0, color: folderColor } },
17574
- import_react16.default.createElement(open ? import_dsh_client_ui_primitives5.IconFolderOpen16 : import_dsh_client_ui_primitives5.IconFolderClose16, { size: 16 })
18055
+ import_react17.default.createElement(open ? import_dsh_client_ui_primitives6.IconFolderOpen16 : import_dsh_client_ui_primitives6.IconFolderClose16, { size: 16 })
17575
18056
  ),
17576
18057
  // Name
17577
- import_react16.default.createElement("span", {
18058
+ import_react17.default.createElement("span", {
17578
18059
  style: {
17579
18060
  overflow: "hidden",
17580
18061
  textOverflow: "ellipsis",
@@ -17582,12 +18063,12 @@ function FolderTreeRow(props) {
17582
18063
  }
17583
18064
  }, name)
17584
18065
  ),
17585
- open && hasChildren && import_react16.default.createElement("div", null, children)
18066
+ open && hasChildren && import_react17.default.createElement("div", null, children)
17586
18067
  );
17587
18068
  }
17588
18069
  function FileRow(props) {
17589
18070
  const { name, depth, size, isSelected = false, onClick } = props;
17590
- return import_react16.default.createElement(
18071
+ return import_react17.default.createElement(
17591
18072
  "div",
17592
18073
  {
17593
18074
  role: "button",
@@ -17622,10 +18103,10 @@ function FileRow(props) {
17622
18103
  }
17623
18104
  },
17624
18105
  // File icon placeholder (Harness doesn't have a specific file icon; use chevron space)
17625
- import_react16.default.createElement("span", {
18106
+ import_react17.default.createElement("span", {
17626
18107
  style: { width: "14px", flexShrink: 0 }
17627
18108
  }),
17628
- import_react16.default.createElement("span", {
18109
+ import_react17.default.createElement("span", {
17629
18110
  style: {
17630
18111
  overflow: "hidden",
17631
18112
  textOverflow: "ellipsis",
@@ -17634,7 +18115,7 @@ function FileRow(props) {
17634
18115
  minWidth: 0
17635
18116
  }
17636
18117
  }, name),
17637
- size !== void 0 && import_react16.default.createElement("span", {
18118
+ size !== void 0 && import_react17.default.createElement("span", {
17638
18119
  style: {
17639
18120
  fontSize: "11px",
17640
18121
  color: "var(--dsw-alias-label-tertiary)",
@@ -17653,7 +18134,7 @@ var OUTPUT_FILTER_TABS = [
17653
18134
  ];
17654
18135
  function TargetIcon(props) {
17655
18136
  const s = props.size ?? 16;
17656
- return import_react17.default.createElement(
18137
+ return import_react18.default.createElement(
17657
18138
  "svg",
17658
18139
  {
17659
18140
  width: s,
@@ -17667,13 +18148,13 @@ function TargetIcon(props) {
17667
18148
  "aria-hidden": true,
17668
18149
  style: { flexShrink: 0 }
17669
18150
  },
17670
- import_react17.default.createElement("circle", { cx: "8", cy: "8", r: "6.5" }),
17671
- import_react17.default.createElement("circle", { cx: "8", cy: "8", r: "3.5" }),
17672
- import_react17.default.createElement("circle", { cx: "8", cy: "8", r: "1", fill: "currentColor", stroke: "none" }),
17673
- import_react17.default.createElement("line", { x1: "8", y1: "1", x2: "8", y2: "3" }),
17674
- import_react17.default.createElement("line", { x1: "8", y1: "13", x2: "8", y2: "15" }),
17675
- import_react17.default.createElement("line", { x1: "1", y1: "8", x2: "3", y2: "8" }),
17676
- import_react17.default.createElement("line", { x1: "13", y1: "8", x2: "15", y2: "8" })
18151
+ import_react18.default.createElement("circle", { cx: "8", cy: "8", r: "6.5" }),
18152
+ import_react18.default.createElement("circle", { cx: "8", cy: "8", r: "3.5" }),
18153
+ import_react18.default.createElement("circle", { cx: "8", cy: "8", r: "1", fill: "currentColor", stroke: "none" }),
18154
+ import_react18.default.createElement("line", { x1: "8", y1: "1", x2: "8", y2: "3" }),
18155
+ import_react18.default.createElement("line", { x1: "8", y1: "13", x2: "8", y2: "15" }),
18156
+ import_react18.default.createElement("line", { x1: "1", y1: "8", x2: "3", y2: "8" }),
18157
+ import_react18.default.createElement("line", { x1: "13", y1: "8", x2: "15", y2: "8" })
17677
18158
  );
17678
18159
  }
17679
18160
  function entryContainsSelected(entry, selectedPath) {
@@ -17684,12 +18165,12 @@ function entryContainsSelected(entry, selectedPath) {
17684
18165
  }
17685
18166
  function TargetRow(props) {
17686
18167
  const { targetId, target, current, defaultOpen, containsSelected, children } = props;
17687
- const [open, setOpen] = (0, import_react17.useState)(defaultOpen);
18168
+ const [open, setOpen] = (0, import_react18.useState)(defaultOpen);
17688
18169
  const iconColor = containsSelected ? "var(--dsw-alias-state-business-primary)" : current ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-label-secondary)";
17689
- return import_react17.default.createElement(
18170
+ return import_react18.default.createElement(
17690
18171
  "div",
17691
18172
  null,
17692
- import_react17.default.createElement(
18173
+ import_react18.default.createElement(
17693
18174
  "div",
17694
18175
  {
17695
18176
  role: "button",
@@ -17725,7 +18206,7 @@ function TargetRow(props) {
17725
18206
  e.currentTarget.style.background = "transparent";
17726
18207
  }
17727
18208
  },
17728
- import_react17.default.createElement("span", {
18209
+ import_react18.default.createElement("span", {
17729
18210
  style: {
17730
18211
  color: "var(--dsw-alias-label-tertiary)",
17731
18212
  flexShrink: 0,
@@ -17735,22 +18216,22 @@ function TargetRow(props) {
17735
18216
  width: "12px"
17736
18217
  }
17737
18218
  }, open ? "\u25BE" : "\u25B8"),
17738
- import_react17.default.createElement("span", {
18219
+ import_react18.default.createElement("span", {
17739
18220
  style: {
17740
18221
  color: iconColor,
17741
18222
  flexShrink: 0,
17742
18223
  display: "flex",
17743
18224
  alignItems: "center"
17744
18225
  }
17745
- }, import_react17.default.createElement(TargetIcon, { size: 16 })),
17746
- import_react17.default.createElement("span", {
18226
+ }, import_react18.default.createElement(TargetIcon, { size: 16 })),
18227
+ import_react18.default.createElement("span", {
17747
18228
  style: {
17748
18229
  overflow: "hidden",
17749
18230
  textOverflow: "ellipsis",
17750
18231
  whiteSpace: "nowrap"
17751
18232
  }
17752
18233
  }, target),
17753
- current && import_react17.default.createElement("span", {
18234
+ current && import_react18.default.createElement("span", {
17754
18235
  style: {
17755
18236
  fontSize: "10px",
17756
18237
  color: "var(--dsw-alias-state-business-primary)",
@@ -17763,19 +18244,19 @@ function TargetRow(props) {
17763
18244
  }
17764
18245
  }, "active")
17765
18246
  ),
17766
- open && import_react17.default.createElement("div", { style: { marginLeft: "20px" } }, children)
18247
+ open && import_react18.default.createElement("div", { style: { marginLeft: "20px" } }, children)
17767
18248
  );
17768
18249
  }
17769
18250
  function OutputView(props) {
17770
- const { snapshot, command, sessionId } = props;
17771
- const [filter, setFilter] = (0, import_react17.useState)("all");
17772
- const [preview, setPreview] = (0, import_react17.useState)(null);
17773
- const [previewContent, setPreviewContent] = (0, import_react17.useState)(null);
17774
- const [previewLoading, setPreviewLoading] = (0, import_react17.useState)(false);
17775
- const [outputTree, setOutputTree] = (0, import_react17.useState)(null);
17776
- const [outputLoading, setOutputLoading] = (0, import_react17.useState)(false);
17777
- const [outputError, setOutputError] = (0, import_react17.useState)(null);
17778
- (0, import_react17.useEffect)(() => {
18251
+ const { snapshot, command, sessionId, focusOutput } = props;
18252
+ const [filter, setFilter] = (0, import_react18.useState)("all");
18253
+ const [preview, setPreview] = (0, import_react18.useState)(null);
18254
+ const [previewContent, setPreviewContent] = (0, import_react18.useState)(null);
18255
+ const [previewLoading, setPreviewLoading] = (0, import_react18.useState)(false);
18256
+ const [outputTree, setOutputTree] = (0, import_react18.useState)(null);
18257
+ const [outputLoading, setOutputLoading] = (0, import_react18.useState)(false);
18258
+ const [outputError, setOutputError] = (0, import_react18.useState)(null);
18259
+ (0, import_react18.useEffect)(() => {
17779
18260
  if (snapshot === null) return;
17780
18261
  let cancelled = false;
17781
18262
  setOutputLoading(true);
@@ -17801,7 +18282,7 @@ function OutputView(props) {
17801
18282
  const targetTrees = outputTree !== null ? [outputTree] : [];
17802
18283
  const selectedPath = preview?.entry.path ?? null;
17803
18284
  const selectedTargetId = preview?.targetId ?? null;
17804
- const filteredTargets = (0, import_react17.useMemo)(() => {
18285
+ const filteredTargets = (0, import_react18.useMemo)(() => {
17805
18286
  const base = filter === "all" ? targetTrees : (() => {
17806
18287
  const categoryMap = {
17807
18288
  all: [],
@@ -17822,7 +18303,7 @@ function OutputView(props) {
17822
18303
  )
17823
18304
  }));
17824
18305
  }, [targetTrees, filter, selectedPath, selectedTargetId]);
17825
- const openPreview = (0, import_react17.useCallback)(async (entry, targetId) => {
18306
+ const openPreview = (0, import_react18.useCallback)(async (entry, targetId) => {
17826
18307
  if (entry.kind === "directory") return;
17827
18308
  setPreview({ entry, targetId });
17828
18309
  setPreviewContent(null);
@@ -17845,27 +18326,37 @@ function OutputView(props) {
17845
18326
  setPreviewLoading(false);
17846
18327
  }
17847
18328
  }, [command, sessionId]);
17848
- const closePreview = (0, import_react17.useCallback)(() => {
18329
+ const closePreview = (0, import_react18.useCallback)(() => {
17849
18330
  setPreview(null);
17850
18331
  setPreviewContent(null);
17851
18332
  }, []);
18333
+ const focusAppliedRef = (0, import_react18.useRef)(void 0);
18334
+ (0, import_react18.useEffect)(() => {
18335
+ if (focusOutput === void 0 || focusOutput === focusAppliedRef.current) return;
18336
+ if (outputTree === null) return;
18337
+ focusAppliedRef.current = focusOutput;
18338
+ const entry = findEntryByPath(outputTree.children, focusOutput);
18339
+ if (entry !== void 0) {
18340
+ openPreview(entry, outputTree.targetId);
18341
+ }
18342
+ }, [focusOutput, outputTree, openPreview]);
17852
18343
  const initialLoading = outputTree === null && outputLoading;
17853
18344
  if (initialLoading) {
17854
- return import_react17.default.createElement("div", {
18345
+ return import_react18.default.createElement("div", {
17855
18346
  style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17856
18347
  }, "Loading output tree...");
17857
18348
  }
17858
18349
  if (outputError !== null) {
17859
- return import_react17.default.createElement(
18350
+ return import_react18.default.createElement(
17860
18351
  "div",
17861
18352
  { style: { padding: "24px" } },
17862
- import_react17.default.createElement("div", {
18353
+ import_react18.default.createElement("div", {
17863
18354
  style: { color: "var(--dsw-status-error)", marginBottom: "12px", fontSize: "14px" }
17864
18355
  }, "Unable to load output tree"),
17865
- import_react17.default.createElement("div", {
18356
+ import_react18.default.createElement("div", {
17866
18357
  style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px", marginBottom: "12px" }
17867
18358
  }, outputError),
17868
- import_react17.default.createElement("button", {
18359
+ import_react18.default.createElement("button", {
17869
18360
  onClick: () => {
17870
18361
  setOutputError(null);
17871
18362
  setOutputTree(null);
@@ -17895,48 +18386,66 @@ function OutputView(props) {
17895
18386
  );
17896
18387
  }
17897
18388
  if (snapshot === null) {
17898
- return import_react17.default.createElement("div", {
18389
+ return import_react18.default.createElement("div", {
17899
18390
  style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17900
18391
  }, "No output data");
17901
18392
  }
17902
18393
  if (targetTrees.length === 0) {
17903
- return import_react17.default.createElement("div", {
18394
+ return import_react18.default.createElement("div", {
17904
18395
  style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17905
18396
  }, "No output files");
17906
18397
  }
17907
- return import_react17.default.createElement(
18398
+ return import_react18.default.createElement(
17908
18399
  "div",
17909
18400
  {
17910
- style: { display: "flex", gap: "16px", minHeight: "400px", overflow: "visible" }
18401
+ style: { display: "flex", gap: "16px", flex: 1, height: "100%", minHeight: 0, minWidth: 0, overflow: "hidden" }
17911
18402
  },
17912
18403
  // Left: file tree
17913
- import_react17.default.createElement(
18404
+ import_react18.default.createElement(
17914
18405
  "div",
17915
18406
  {
17916
18407
  style: {
17917
18408
  flex: "0 0 320px",
17918
- overflow: "auto",
17919
- borderRight: "1px solid var(--dsw-alias-border-l2)",
17920
- paddingRight: "12px",
18409
+ minHeight: 0,
18410
+ overflow: "hidden",
17921
18411
  display: "flex",
17922
- flexDirection: "column"
18412
+ flexDirection: "column",
18413
+ borderRight: "1px solid var(--dsw-alias-border-l2)",
18414
+ paddingRight: "12px"
17923
18415
  }
17924
18416
  },
17925
- import_react17.default.createElement(
18417
+ // Filter tabs (fixed)
18418
+ import_react18.default.createElement(
17926
18419
  "div",
17927
- { style: { marginBottom: "12px" } },
17928
- import_react17.default.createElement(PentesterTabBar, {
18420
+ {
18421
+ style: {
18422
+ flex: "none",
18423
+ marginBottom: "12px",
18424
+ paddingTop: "2px"
18425
+ }
18426
+ },
18427
+ import_react18.default.createElement(PentesterTabBar, {
17929
18428
  items: OUTPUT_FILTER_TABS,
17930
18429
  active: filter,
17931
18430
  onSelect: (id) => setFilter(id),
17932
18431
  variant: "compact"
17933
18432
  })
17934
18433
  ),
17935
- // ── Tree: "Targets" section label + target rows at root depth ─────
17936
- import_react17.default.createElement(
18434
+ // ── TreeViewport (scrollable) ──────────────────────────────────
18435
+ import_react18.default.createElement(
17937
18436
  "div",
17938
- { style: { flex: 1 } },
17939
- import_react17.default.createElement("div", {
18437
+ {
18438
+ style: {
18439
+ flex: 1,
18440
+ minHeight: 0,
18441
+ minWidth: 0,
18442
+ overflowY: "auto",
18443
+ overflowX: "hidden",
18444
+ overscrollBehaviorY: "contain",
18445
+ scrollbarGutter: "stable"
18446
+ }
18447
+ },
18448
+ import_react18.default.createElement("div", {
17940
18449
  style: {
17941
18450
  fontSize: "11px",
17942
18451
  fontWeight: 600,
@@ -17948,7 +18457,7 @@ function OutputView(props) {
17948
18457
  }
17949
18458
  }, "Targets"),
17950
18459
  ...filteredTargets.map(
17951
- (t) => import_react17.default.createElement(TargetRow, {
18460
+ (t) => import_react18.default.createElement(TargetRow, {
17952
18461
  key: t.targetId,
17953
18462
  targetId: t.targetId,
17954
18463
  target: t.target,
@@ -17963,23 +18472,22 @@ function OutputView(props) {
17963
18472
  )
17964
18473
  ),
17965
18474
  // Right: preview frame
17966
- import_react17.default.createElement(
18475
+ import_react18.default.createElement(
17967
18476
  "div",
17968
18477
  {
17969
18478
  style: {
17970
18479
  flex: 1,
17971
18480
  minWidth: 0,
18481
+ minHeight: 0,
17972
18482
  display: "flex",
17973
18483
  flexDirection: "column",
17974
18484
  overflow: "hidden",
17975
18485
  border: "1px solid var(--dsw-alias-border-l2)",
17976
18486
  borderRadius: "8px",
17977
- background: "var(--dsw-alias-bg-base)",
17978
- minHeight: "320px",
17979
- maxHeight: "clamp(320px, 65vh, 680px)"
18487
+ background: "var(--dsw-alias-bg-base)"
17980
18488
  }
17981
18489
  },
17982
- preview === null ? import_react17.default.createElement("div", {
18490
+ preview === null ? import_react18.default.createElement("div", {
17983
18491
  style: {
17984
18492
  flex: 1,
17985
18493
  display: "flex",
@@ -17988,43 +18496,77 @@ function OutputView(props) {
17988
18496
  color: "var(--dsw-alias-label-tertiary)",
17989
18497
  fontSize: "13px"
17990
18498
  }
17991
- }, "Select a file to preview") : import_react17.default.createElement(
17992
- import_react17.default.Fragment,
18499
+ }, "Select a file to preview") : import_react18.default.createElement(
18500
+ import_react18.default.Fragment,
17993
18501
  null,
17994
- import_react17.default.createElement(
18502
+ // PreviewHeader (fixed)
18503
+ import_react18.default.createElement(
17995
18504
  "div",
17996
18505
  {
17997
18506
  style: {
17998
18507
  flex: "none",
17999
18508
  display: "flex",
18000
18509
  alignItems: "center",
18001
- justifyContent: "space-between",
18002
18510
  padding: "8px 12px",
18003
18511
  borderBottom: "1px solid var(--dsw-alias-border-l2)"
18004
18512
  }
18005
18513
  },
18006
- import_react17.default.createElement("div", {
18007
- style: { fontSize: "13px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
18008
- }, preview.entry.name),
18009
- import_react17.default.createElement("button", {
18010
- onClick: closePreview,
18514
+ // Filename
18515
+ import_react18.default.createElement("div", {
18011
18516
  style: {
18012
- background: "none",
18013
- border: "none",
18014
- color: "var(--dsw-alias-label-tertiary)",
18015
- cursor: "pointer",
18016
- fontSize: "18px",
18017
- padding: "0 4px"
18517
+ flex: 1,
18518
+ minWidth: 0,
18519
+ fontSize: "13px",
18520
+ fontWeight: 600,
18521
+ color: "var(--dsw-alias-label-primary)",
18522
+ overflow: "hidden",
18523
+ textOverflow: "ellipsis",
18524
+ whiteSpace: "nowrap"
18525
+ }
18526
+ }, preview.entry.name),
18527
+ // Actions
18528
+ import_react18.default.createElement(
18529
+ "div",
18530
+ {
18531
+ style: {
18532
+ flex: "none",
18533
+ display: "flex",
18534
+ alignItems: "center",
18535
+ gap: "4px",
18536
+ marginLeft: "8px"
18537
+ }
18018
18538
  },
18019
- "aria-label": "Close preview"
18020
- }, "\xD7")
18539
+ previewContent !== null && previewContent.content !== void 0 && previewContent.mime !== "binary" && import_react18.default.createElement(CopyAction, { text: previewContent.content }),
18540
+ import_react18.default.createElement("button", {
18541
+ onClick: closePreview,
18542
+ style: {
18543
+ background: "none",
18544
+ border: "none",
18545
+ color: "var(--dsw-alias-label-tertiary)",
18546
+ cursor: "pointer",
18547
+ fontSize: "18px",
18548
+ padding: "0 4px"
18549
+ },
18550
+ "aria-label": "Close preview"
18551
+ }, "\xD7")
18552
+ )
18021
18553
  ),
18022
- import_react17.default.createElement(
18554
+ // PreviewBody (scrollable)
18555
+ import_react18.default.createElement(
18023
18556
  "div",
18024
18557
  {
18025
- style: { flex: 1, minHeight: 0, overflow: "auto", padding: "12px" }
18558
+ style: {
18559
+ flex: 1,
18560
+ minHeight: 0,
18561
+ minWidth: 0,
18562
+ overflowY: "auto",
18563
+ overflowX: "auto",
18564
+ overscrollBehaviorY: "contain",
18565
+ scrollbarGutter: "stable",
18566
+ padding: "12px"
18567
+ }
18026
18568
  },
18027
- previewLoading && import_react17.default.createElement("div", {
18569
+ previewLoading && import_react18.default.createElement("div", {
18028
18570
  style: { color: "var(--dsw-alias-label-tertiary)", padding: "16px 0" }
18029
18571
  }, "Loading..."),
18030
18572
  previewContent !== null && renderPreview(previewContent)
@@ -18037,7 +18579,7 @@ function renderOutputTree(entry, depth, targetId, onSelect, selected, selectedPa
18037
18579
  if (entry.kind === "directory" && entry.children !== void 0 && entry.children.length > 0) {
18038
18580
  const isSelectedTarget = selectedTargetId === targetId;
18039
18581
  const containsSelected = isSelectedTarget && entryContainsSelected(entry, selectedPath);
18040
- return import_react17.default.createElement(FolderTreeRow, {
18582
+ return import_react18.default.createElement(FolderTreeRow, {
18041
18583
  key: `${targetId}:${entry.path}`,
18042
18584
  name: entry.name,
18043
18585
  depth,
@@ -18049,7 +18591,7 @@ function renderOutputTree(entry, depth, targetId, onSelect, selected, selectedPa
18049
18591
  });
18050
18592
  }
18051
18593
  const isSelected = selected?.entry.path === entry.path && selected?.targetId === targetId;
18052
- return import_react17.default.createElement(FileRow, {
18594
+ return import_react18.default.createElement(FileRow, {
18053
18595
  key: `${targetId}:${entry.path}`,
18054
18596
  name: entry.name,
18055
18597
  depth,
@@ -18060,33 +18602,33 @@ function renderOutputTree(entry, depth, targetId, onSelect, selected, selectedPa
18060
18602
  }
18061
18603
  function renderPreview(result) {
18062
18604
  if (result.mime === "binary") {
18063
- return import_react17.default.createElement(
18605
+ return import_react18.default.createElement(
18064
18606
  "div",
18065
18607
  {
18066
18608
  style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "13px", padding: "16px" }
18067
18609
  },
18068
- import_react17.default.createElement("div", { style: { marginBottom: "8px" } }, "Preview unavailable"),
18069
- import_react17.default.createElement("div", { style: { fontSize: "12px" } }, `Binary file \xB7 ${formatSize(result.size)}`),
18070
- import_react17.default.createElement("div", { style: { fontSize: "12px" } }, result.path)
18610
+ import_react18.default.createElement("div", { style: { marginBottom: "8px" } }, "Preview unavailable"),
18611
+ import_react18.default.createElement("div", { style: { fontSize: "12px" } }, `Binary file \xB7 ${formatSize(result.size)}`),
18612
+ import_react18.default.createElement("div", { style: { fontSize: "12px" } }, result.path)
18071
18613
  );
18072
18614
  }
18073
18615
  if (result.content === void 0) {
18074
- return import_react17.default.createElement("div", {
18616
+ return import_react18.default.createElement("div", {
18075
18617
  style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "13px" }
18076
18618
  }, "No content available");
18077
18619
  }
18078
18620
  if (result.mime === "markdown") {
18079
- return import_react17.default.createElement(import_dsh_client_ui_primitives6.MarkdownText, { text: result.content });
18621
+ return import_react18.default.createElement(import_dsh_client_ui_primitives7.MarkdownText, { text: result.content });
18080
18622
  }
18081
18623
  if (result.mime === "json") {
18082
18624
  try {
18083
18625
  const parsed = JSON.parse(result.content);
18084
- return import_react17.default.createElement(import_dsh_client_ui_primitives6.JsonTree, { data: parsed, label: result.name });
18626
+ return import_react18.default.createElement(import_dsh_client_ui_primitives7.JsonTree, { data: parsed, label: result.name });
18085
18627
  } catch {
18086
18628
  }
18087
18629
  }
18088
18630
  const lines = result.content.split("\n");
18089
- return import_react17.default.createElement(import_dsh_client_ui_primitives6.ReadBlock, {
18631
+ return import_react18.default.createElement(import_dsh_client_ui_primitives7.ReadBlock, {
18090
18632
  lines: lines.map((text, i) => ({ number: i + 1, text })),
18091
18633
  totalLines: lines.length,
18092
18634
  maxLines: 500
@@ -18109,6 +18651,16 @@ function formatSize(bytes) {
18109
18651
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
18110
18652
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
18111
18653
  }
18654
+ function findEntryByPath(entries, targetPath) {
18655
+ for (const entry of entries) {
18656
+ if (entry.path === targetPath) return entry;
18657
+ if (entry.kind === "directory" && entry.children !== void 0) {
18658
+ const found = findEntryByPath(entry.children, targetPath);
18659
+ if (found !== void 0) return found;
18660
+ }
18661
+ }
18662
+ return void 0;
18663
+ }
18112
18664
 
18113
18665
  // src/ui/client/pentester-view.tsx
18114
18666
  var CORE_REFRESH_MS = 1e4;
@@ -18151,13 +18703,13 @@ function resolveDelegationPresentationState(delegation, sessionList, rootSession
18151
18703
  return "unknown";
18152
18704
  }
18153
18705
  function useDelegationActivity(sessions, rootSessionId, delegations) {
18154
- const delegationSessionIds = (0, import_react18.useMemo)(
18706
+ const delegationSessionIds = (0, import_react19.useMemo)(
18155
18707
  () => new Set(delegations.filter((d) => d.sessionId !== void 0).map((d) => d.sessionId)),
18156
18708
  [delegations]
18157
18709
  );
18158
- const prevSigRef = (0, import_react18.useRef)("");
18159
- const cachedMapRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
18160
- const getSnapshot = (0, import_react18.useCallback)(() => {
18710
+ const prevSigRef = (0, import_react19.useRef)("");
18711
+ const cachedMapRef = (0, import_react19.useRef)(/* @__PURE__ */ new Map());
18712
+ const getSnapshot = (0, import_react19.useCallback)(() => {
18161
18713
  const raw = sessions?.list.getSnapshot();
18162
18714
  if (raw === void 0) return cachedMapRef.current;
18163
18715
  const sig = computeActivitySignature(raw, rootSessionId, delegationSessionIds);
@@ -18171,7 +18723,7 @@ function useDelegationActivity(sessions, rootSessionId, delegations) {
18171
18723
  cachedMapRef.current = map2;
18172
18724
  return map2;
18173
18725
  }, [sessions, rootSessionId, delegationSessionIds, delegations]);
18174
- return (0, import_react18.useSyncExternalStore)(
18726
+ return (0, import_react19.useSyncExternalStore)(
18175
18727
  sessions?.list.subscribe ?? (() => () => {
18176
18728
  }),
18177
18729
  getSnapshot
@@ -18183,7 +18735,7 @@ var RPC_PREFIX = "[PENTESTER_RPC]";
18183
18735
  var isPerf = typeof window !== "undefined" && window.__DSH_PENTESTER_PERF === true;
18184
18736
  function PentesterView(props) {
18185
18737
  const { sessionId, reflect, sessions, useSession, sessionSwitchTimes: sessionSwitchTimes2, sessionPerf } = props;
18186
- const mountTime = (0, import_react18.useRef)(performance.now());
18738
+ const mountTime = (0, import_react19.useRef)(performance.now());
18187
18739
  const switchTime = sessionSwitchTimes2?.get(sessionId);
18188
18740
  if (isPerf) {
18189
18741
  console.log(`${PERF_PREFIX} mount=0ms`);
@@ -18192,8 +18744,8 @@ function PentesterView(props) {
18192
18744
  }
18193
18745
  }
18194
18746
  const openState = useSession?.((s) => s.openState);
18195
- const prevOpenState = (0, import_react18.useRef)(void 0);
18196
- (0, import_react18.useEffect)(() => {
18747
+ const prevOpenState = (0, import_react19.useRef)(void 0);
18748
+ (0, import_react19.useEffect)(() => {
18197
18749
  if (!sessionPerf || openState === void 0) return;
18198
18750
  if (openState !== prevOpenState.current) {
18199
18751
  const elapsed = (performance.now() - mountTime.current).toFixed(0);
@@ -18219,27 +18771,27 @@ function PentesterView(props) {
18219
18771
  prevOpenState.current = openState;
18220
18772
  }
18221
18773
  }, [openState, sessionPerf, switchTime]);
18222
- const [tab, setTab] = (0, import_react18.useState)("overview");
18223
- const [dataState, setDataState] = (0, import_react18.useState)({ kind: "initial" });
18224
- const [requestState, setRequestState] = (0, import_react18.useState)("idle");
18225
- const [navFocus, setNavFocus] = (0, import_react18.useState)({});
18226
- const [activeTargetId, setActiveTargetId] = (0, import_react18.useState)(null);
18227
- const requestSeqRef = (0, import_react18.useRef)(0);
18228
- const pendingRef = (0, import_react18.useRef)(null);
18229
- const mountedRef = (0, import_react18.useRef)(true);
18230
- const firstLoadDone = (0, import_react18.useRef)(false);
18231
- const pollingPausedRef = (0, import_react18.useRef)(false);
18232
- const backoffRef = (0, import_react18.useRef)(0);
18233
- const timerRef = (0, import_react18.useRef)(null);
18234
- const snapshotRef = (0, import_react18.useRef)(null);
18235
- const pentesterCommand = (0, import_react18.useCallback)((input2, timeoutMs) => {
18774
+ const [tab, setTab] = (0, import_react19.useState)("overview");
18775
+ const [dataState, setDataState] = (0, import_react19.useState)({ kind: "initial" });
18776
+ const [requestState, setRequestState] = (0, import_react19.useState)("idle");
18777
+ const [navFocus, setNavFocus] = (0, import_react19.useState)({});
18778
+ const [activeTargetId, setActiveTargetId] = (0, import_react19.useState)(null);
18779
+ const requestSeqRef = (0, import_react19.useRef)(0);
18780
+ const pendingRef = (0, import_react19.useRef)(null);
18781
+ const mountedRef = (0, import_react19.useRef)(true);
18782
+ const firstLoadDone = (0, import_react19.useRef)(false);
18783
+ const pollingPausedRef = (0, import_react19.useRef)(false);
18784
+ const backoffRef = (0, import_react19.useRef)(0);
18785
+ const timerRef = (0, import_react19.useRef)(null);
18786
+ const snapshotRef = (0, import_react19.useRef)(null);
18787
+ const pentesterCommand = (0, import_react19.useCallback)((input2, timeoutMs) => {
18236
18788
  const currentFace = reflect?.get("remote.pentesterView");
18237
18789
  return pentesterViewCommand(currentFace, input2, timeoutMs);
18238
18790
  }, [reflect]);
18239
18791
  const snapshot = dataState.kind === "bound" ? dataState.snapshot : null;
18240
18792
  const delegations = snapshot?.delegations ?? [];
18241
18793
  const liveActivity = useDelegationActivity(sessions, sessionId, delegations);
18242
- const loadSnapshot = (0, import_react18.useCallback)(async (reason) => {
18794
+ const loadSnapshot = (0, import_react19.useCallback)(async (reason) => {
18243
18795
  if (reason !== "retry" && reason !== "refresh" && getPentesterEndpointState() === "missing") {
18244
18796
  if (isPerf) console.log(`${RPC_PREFIX} circuit-open endpoint=missing reason=${reason}`);
18245
18797
  return;
@@ -18357,7 +18909,7 @@ Client: ${mismatch.client}`,
18357
18909
  }
18358
18910
  }
18359
18911
  }, [pentesterCommand, sessionId, switchTime]);
18360
- (0, import_react18.useEffect)(() => {
18912
+ (0, import_react19.useEffect)(() => {
18361
18913
  mountedRef.current = true;
18362
18914
  pollingPausedRef.current = false;
18363
18915
  backoffRef.current = 0;
@@ -18385,7 +18937,7 @@ Client: ${mismatch.client}`,
18385
18937
  if (timerRef.current !== null) clearTimeout(timerRef.current);
18386
18938
  };
18387
18939
  }, [loadSnapshot]);
18388
- (0, import_react18.useEffect)(() => {
18940
+ (0, import_react19.useEffect)(() => {
18389
18941
  const onVisible = () => {
18390
18942
  if (document.visibilityState === "visible" && mountedRef.current && firstLoadDone.current && !pollingPausedRef.current) {
18391
18943
  loadSnapshot("visible");
@@ -18394,11 +18946,11 @@ Client: ${mismatch.client}`,
18394
18946
  document.addEventListener("visibilitychange", onVisible);
18395
18947
  return () => document.removeEventListener("visibilitychange", onVisible);
18396
18948
  }, [loadSnapshot]);
18397
- const navigate = (0, import_react18.useCallback)((targetTab, focus) => {
18949
+ const navigate = (0, import_react19.useCallback)((targetTab, focus) => {
18398
18950
  setTab(targetTab);
18399
18951
  if (focus !== void 0) setNavFocus(focus);
18400
18952
  }, []);
18401
- const openSession = (0, import_react18.useCallback)((id) => {
18953
+ const openSession = (0, import_react19.useCallback)((id) => {
18402
18954
  sessions?.open(id);
18403
18955
  }, [sessions]);
18404
18956
  const isInitialLoading = dataState.kind === "initial" && requestState === "pending";
@@ -18406,28 +18958,28 @@ Client: ${mismatch.client}`,
18406
18958
  const errorMsg = dataState.kind === "error" ? dataState.error : null;
18407
18959
  const errorCode = dataState.kind === "error" ? dataState.code : null;
18408
18960
  const hasSnapshot = snapshot !== null;
18409
- return import_react18.default.createElement(
18961
+ return import_react19.default.createElement(
18410
18962
  "div",
18411
18963
  {
18412
18964
  "data-testid": "pentester-view",
18413
18965
  "data-pentester-view": "",
18966
+ "data-conversation-composer-overlay": "",
18414
18967
  style: {
18415
18968
  display: "flex",
18416
18969
  flexDirection: "column",
18417
18970
  width: "100%",
18418
- minHeight: "100%",
18419
- overflow: "visible"
18971
+ height: "100%",
18972
+ minHeight: 0,
18973
+ overflow: "hidden"
18420
18974
  }
18421
18975
  },
18422
18976
  // ── Layer 1: TabsBar (sticky) ────────────────────────────────────────
18423
- import_react18.default.createElement(
18977
+ import_react19.default.createElement(
18424
18978
  "div",
18425
18979
  {
18426
18980
  "data-testid": "pentester-tabs-bar",
18427
18981
  style: {
18428
18982
  flex: "none",
18429
- position: "sticky",
18430
- top: 0,
18431
18983
  zIndex: 29,
18432
18984
  background: "var(--dsw-alias-bg-base)",
18433
18985
  borderBottom: "1px solid var(--dsw-alias-border-l2)",
@@ -18436,7 +18988,7 @@ Client: ${mismatch.client}`,
18436
18988
  boxSizing: "border-box"
18437
18989
  }
18438
18990
  },
18439
- import_react18.default.createElement(PentesterTabBar, {
18991
+ import_react19.default.createElement(PentesterTabBar, {
18440
18992
  items: PENTESTER_TABS.map((t) => ({
18441
18993
  id: t.id,
18442
18994
  label: t.id.charAt(0).toUpperCase() + t.id.slice(1)
@@ -18447,7 +18999,7 @@ Client: ${mismatch.client}`,
18447
18999
  ),
18448
19000
  // ── Refresh error banner ──────────────────────────────────────────────
18449
19001
  // Stale-while-revalidate: error + last good snapshot → show banner
18450
- errorMsg !== null && hasSnapshot && import_react18.default.createElement(
19002
+ errorMsg !== null && hasSnapshot && import_react19.default.createElement(
18451
19003
  "div",
18452
19004
  {
18453
19005
  style: {
@@ -18461,8 +19013,8 @@ Client: ${mismatch.client}`,
18461
19013
  alignItems: "center"
18462
19014
  }
18463
19015
  },
18464
- import_react18.default.createElement("span", null, errorMsg),
18465
- import_react18.default.createElement("button", {
19016
+ import_react19.default.createElement("span", null, errorMsg),
19017
+ import_react19.default.createElement("button", {
18466
19018
  onClick: () => {
18467
19019
  if (pendingRef.current === null) {
18468
19020
  void loadSnapshot("retry");
@@ -18482,29 +19034,32 @@ Client: ${mismatch.client}`,
18482
19034
  }
18483
19035
  }, isRetrying ? "Retrying\u2026" : "Retry")
18484
19036
  ),
18485
- // ── Body: flex 1, no scroll ──────────────────────────────────────────
18486
- import_react18.default.createElement(
19037
+ // ── Body: bounded flex viewport ────────────────────────────────────
19038
+ import_react19.default.createElement(
18487
19039
  "div",
18488
19040
  {
18489
19041
  style: {
18490
19042
  flex: 1,
18491
19043
  minHeight: 0,
18492
- overflow: "visible",
18493
- padding: "16px"
19044
+ overflow: "hidden",
19045
+ display: "flex",
19046
+ flexDirection: "column",
19047
+ padding: "16px",
19048
+ boxSizing: "border-box"
18494
19049
  }
18495
19050
  },
18496
19051
  // Initial loading: shell is already visible, show skeleton
18497
- isInitialLoading && import_react18.default.createElement("div", {
19052
+ isInitialLoading && import_react19.default.createElement("div", {
18498
19053
  style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
18499
19054
  }, "Loading..."),
18500
19055
  // Error state (no last good snapshot)
18501
- dataState.kind === "error" && !hasSnapshot && import_react18.default.createElement(
19056
+ dataState.kind === "error" && !hasSnapshot && import_react19.default.createElement(
18502
19057
  "div",
18503
19058
  { style: { padding: "24px" } },
18504
- import_react18.default.createElement("div", {
19059
+ import_react19.default.createElement("div", {
18505
19060
  style: { color: "var(--dsw-status-error)", marginBottom: "12px", fontSize: "14px" }
18506
19061
  }, errorMsg ?? "Unknown error"),
18507
- import_react18.default.createElement("button", {
19062
+ import_react19.default.createElement("button", {
18508
19063
  onClick: () => {
18509
19064
  if (pendingRef.current === null) {
18510
19065
  void loadSnapshot("retry");
@@ -18524,20 +19079,20 @@ Client: ${mismatch.client}`,
18524
19079
  }, isRetrying ? "Retrying\u2026" : "Retry")
18525
19080
  ),
18526
19081
  // Unbound state
18527
- dataState.kind === "unbound" && import_react18.default.createElement(
19082
+ dataState.kind === "unbound" && import_react19.default.createElement(
18528
19083
  "div",
18529
19084
  { style: { padding: "24px" } },
18530
- import_react18.default.createElement("div", {
19085
+ import_react19.default.createElement("div", {
18531
19086
  style: { fontSize: "14px", color: "var(--dsw-alias-label-tertiary)", marginBottom: "16px" }
18532
19087
  }, "This session is not bound to a pentest target."),
18533
- dataState.targets.length > 0 && import_react18.default.createElement(
19088
+ dataState.targets.length > 0 && import_react19.default.createElement(
18534
19089
  "div",
18535
19090
  { style: { marginBottom: "12px" } },
18536
- import_react18.default.createElement("div", {
19091
+ import_react19.default.createElement("div", {
18537
19092
  style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px", color: "var(--dsw-alias-label-primary)" }
18538
19093
  }, "Existing targets"),
18539
19094
  ...dataState.targets.map(
18540
- (t) => import_react18.default.createElement(
19095
+ (t) => import_react19.default.createElement(
18541
19096
  "div",
18542
19097
  {
18543
19098
  key: t.id,
@@ -18552,17 +19107,17 @@ Client: ${mismatch.client}`,
18552
19107
  fontSize: "13px"
18553
19108
  }
18554
19109
  },
18555
- import_react18.default.createElement(
19110
+ import_react19.default.createElement(
18556
19111
  "div",
18557
19112
  null,
18558
- import_react18.default.createElement("div", { style: { fontWeight: 500 } }, t.target),
18559
- import_react18.default.createElement(
19113
+ import_react19.default.createElement("div", { style: { fontWeight: 500 } }, t.target),
19114
+ import_react19.default.createElement(
18560
19115
  "div",
18561
19116
  { style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px" } },
18562
19117
  `${t.currentStage ?? "unknown"} \xB7 ${t.status}`
18563
19118
  )
18564
19119
  ),
18565
- t.rootSessionId !== null && import_react18.default.createElement("button", {
19120
+ t.rootSessionId !== null && import_react19.default.createElement("button", {
18566
19121
  onClick: () => sessions?.open(t.rootSessionId),
18567
19122
  style: {
18568
19123
  padding: "4px 12px",
@@ -18577,7 +19132,7 @@ Client: ${mismatch.client}`,
18577
19132
  )
18578
19133
  )
18579
19134
  ),
18580
- import_react18.default.createElement("button", {
19135
+ import_react19.default.createElement("button", {
18581
19136
  onClick: () => {
18582
19137
  if (pendingRef.current === null) {
18583
19138
  void loadSnapshot("refresh");
@@ -18597,38 +19152,59 @@ Client: ${mismatch.client}`,
18597
19152
  }
18598
19153
  }, "Refresh")
18599
19154
  ),
18600
- // Bound content
18601
- dataState.kind === "bound" && import_react18.default.createElement(
18602
- import_react18.default.Fragment,
18603
- null,
18604
- tab === "overview" && import_react18.default.createElement(OverviewView, {
18605
- snapshot,
18606
- liveActivity,
18607
- onNavigate: navigate,
18608
- onOpenSession: openSession
18609
- }),
18610
- tab === "stages" && import_react18.default.createElement(StagesView, {
18611
- snapshot,
18612
- liveActivity,
18613
- onNavigate: navigate,
18614
- onOpenSession: openSession,
18615
- focusStage: navFocus.stage
18616
- }),
18617
- tab === "findings" && import_react18.default.createElement(FindingsView, {
19155
+ // Bound content — wrapped in a bounded viewport
19156
+ dataState.kind === "bound" && import_react19.default.createElement(
19157
+ "div",
19158
+ {
19159
+ style: {
19160
+ flex: 1,
19161
+ minHeight: 0,
19162
+ minWidth: 0,
19163
+ overflow: "hidden",
19164
+ display: "flex",
19165
+ flexDirection: "column"
19166
+ }
19167
+ },
19168
+ tab === "overview" && import_react19.default.createElement(
19169
+ ScrollablePage,
19170
+ null,
19171
+ import_react19.default.createElement(OverviewView, {
19172
+ snapshot,
19173
+ liveActivity,
19174
+ onNavigate: navigate,
19175
+ onOpenSession: openSession
19176
+ })
19177
+ ),
19178
+ tab === "stages" && import_react19.default.createElement(
19179
+ ScrollablePage,
19180
+ null,
19181
+ import_react19.default.createElement(StagesView, {
19182
+ snapshot,
19183
+ liveActivity,
19184
+ onNavigate: navigate,
19185
+ onOpenSession: openSession,
19186
+ focusStage: navFocus.stage
19187
+ })
19188
+ ),
19189
+ tab === "findings" && import_react19.default.createElement(FindingsView, {
18618
19190
  snapshot,
18619
19191
  command: pentesterCommand,
18620
19192
  sessionId,
18621
19193
  onNavigate: navigate,
18622
19194
  focusFinding: navFocus.finding
18623
19195
  }),
18624
- tab === "trace" && import_react18.default.createElement(TraceView, {
18625
- snapshot,
18626
- liveActivity,
18627
- onNavigate: navigate,
18628
- onOpenSession: openSession,
18629
- activeTargetId
18630
- }),
18631
- tab === "output" && import_react18.default.createElement(OutputView, {
19196
+ tab === "trace" && import_react19.default.createElement(
19197
+ ScrollablePage,
19198
+ null,
19199
+ import_react19.default.createElement(TraceView, {
19200
+ snapshot,
19201
+ liveActivity,
19202
+ onNavigate: navigate,
19203
+ onOpenSession: openSession,
19204
+ activeTargetId
19205
+ })
19206
+ ),
19207
+ tab === "output" && import_react19.default.createElement(OutputView, {
18632
19208
  snapshot,
18633
19209
  command: pentesterCommand,
18634
19210
  sessionId,
@@ -18638,6 +19214,17 @@ Client: ${mismatch.client}`,
18638
19214
  )
18639
19215
  );
18640
19216
  }
19217
+ function ScrollablePage(props) {
19218
+ return import_react19.default.createElement("div", {
19219
+ style: {
19220
+ flex: 1,
19221
+ minHeight: 0,
19222
+ overflowY: "auto",
19223
+ overflowX: "hidden",
19224
+ overscrollBehavior: "contain"
19225
+ }
19226
+ }, props.children);
19227
+ }
18641
19228
 
18642
19229
  // src/ui/client/pentester/locales.ts
18643
19230
  var NS = "dsh-pentester-view";
@@ -18719,7 +19306,7 @@ var en = {
18719
19306
  };
18720
19307
 
18721
19308
  // src/build-id.ts
18722
- var BUILD_ID = "2.2.0+478786d";
19309
+ var BUILD_ID = "2.4.0+3a85fc6";
18723
19310
 
18724
19311
  // src/ui/client/plugin.tsx
18725
19312
  var SESSION_SWITCH_PERF = "[SESSION_SWITCH_PERF]";
@@ -18747,7 +19334,7 @@ function getRemoteMountError() {
18747
19334
  }
18748
19335
  var inject = ["slots", "remote", "sessions", "locale"];
18749
19336
  function apply(ctx) {
18750
- console.log(`[dsh-pentester] client build=${BUILD_ID}`);
19337
+ console.log(`[dsh-pentester/client] build=${BUILD_ID}`);
18751
19338
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "pentester: locale");
18752
19339
  ctx.effect(() => {
18753
19340
  let disposeMount;
@@ -18780,17 +19367,20 @@ function apply(ctx) {
18780
19367
  if (result.ok && result.value !== null && typeof result.value === "object" && "ok" in result.value) {
18781
19368
  const inner = result.value.value;
18782
19369
  if (inner?.ready === true && typeof inner.buildId === "string") {
19370
+ console.log(`[dsh-pentester/client] health host=${inner.buildId} client=${BUILD_ID}`);
18783
19371
  if (inner.buildId === BUILD_ID) {
19372
+ if (buildMismatch !== null) {
19373
+ console.log(`[dsh-pentester/client] build mismatch cleared (was host=${buildMismatch.host} client=${buildMismatch.client})`);
19374
+ }
18784
19375
  buildMismatch = null;
18785
- console.log(`[dsh-pentester] health check passed host=${inner.buildId} client=${BUILD_ID}`);
18786
19376
  } else {
18787
19377
  buildMismatch = { host: inner.buildId, client: BUILD_ID };
18788
- console.error(`[dsh-pentester] BUILD MISMATCH host=${inner.buildId} client=${BUILD_ID}`);
19378
+ console.error(`[dsh-pentester/client] build mismatch set host=${inner.buildId} client=${BUILD_ID}`);
18789
19379
  }
18790
19380
  }
18791
19381
  }
18792
19382
  } catch (error51) {
18793
- console.warn("[dsh-pentester] build health check unavailable", error51);
19383
+ console.warn("[dsh-pentester/client] build health check unavailable", error51);
18794
19384
  }
18795
19385
  })();
18796
19386
  }).catch((error51) => {
@@ -18816,7 +19406,7 @@ function apply(ctx) {
18816
19406
  return { command: (input2) => dockerCommand(face, input2) };
18817
19407
  }
18818
19408
  },
18819
- (props) => import_react19.default.createElement(PentesterConfigSection, { ...props })
19409
+ (props) => import_react20.default.createElement(PentesterConfigSection, { ...props })
18820
19410
  )
18821
19411
  );
18822
19412
  ctx.slots.inject("conversation.view", () => {
@@ -18863,7 +19453,7 @@ function apply(ctx) {
18863
19453
  }
18864
19454
  },
18865
19455
  (props) => {
18866
- return import_react19.default.createElement(PentesterView, {
19456
+ return import_react20.default.createElement(PentesterView, {
18867
19457
  sessionId: props.sessionId,
18868
19458
  reflect: props.reflect,
18869
19459
  sessions: props.sessions,