dsh-pentester 1.0.0 → 2.2.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
@@ -1,8 +1,10 @@
1
1
  window.__ModuleLoader__.load({ id: 'dsh-pentester', factory: (require) => { var module = { exports: {} }; var exports = module.exports;
2
2
  "use strict";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,15 +18,29 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/ui/client/plugin.tsx
22
32
  var plugin_exports = {};
23
33
  __export(plugin_exports, {
24
34
  apply: () => apply,
25
- inject: () => inject
35
+ getBuildMismatch: () => getBuildMismatch,
36
+ getPentesterEndpointState: () => getPentesterEndpointState2,
37
+ getRemoteMountError: () => getRemoteMountError,
38
+ inject: () => inject,
39
+ isRemoteMounted: () => isRemoteMounted,
40
+ setPentesterEndpointState: () => setPentesterEndpointState
26
41
  });
27
42
  module.exports = __toCommonJS(plugin_exports);
43
+ var import_react19 = __toESM(require("react"), 1);
28
44
 
29
45
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
30
46
  var external_exports = {};
@@ -14591,6 +14607,147 @@ var PENTESTER_REMOTE = {
14591
14607
  descriptors: [PENTESTER_DOCKER_INVOCATION]
14592
14608
  };
14593
14609
 
14610
+ // src/ui-view/invocations.ts
14611
+ var pentesterViewInputSchema = external_exports.discriminatedUnion("kind", [
14612
+ external_exports.object({ kind: external_exports.literal("health") }),
14613
+ external_exports.object({ kind: external_exports.literal("snapshot"), sessionId: external_exports.string() }),
14614
+ external_exports.object({ kind: external_exports.literal("listOutput"), sessionId: external_exports.string(), targetId: external_exports.string().optional() }),
14615
+ external_exports.object({ kind: external_exports.literal("readOutput"), sessionId: external_exports.string(), targetId: external_exports.string().optional(), path: external_exports.string() }),
14616
+ external_exports.object({ kind: external_exports.literal("readFinding"), sessionId: external_exports.string(), detailFile: external_exports.string() })
14617
+ ]);
14618
+ var snapshotResultSchema = external_exports.object({
14619
+ run: external_exports.union([external_exports.object({
14620
+ id: external_exports.string(),
14621
+ status: external_exports.enum(["active", "completed"]),
14622
+ currentStage: external_exports.string().nullable(),
14623
+ targets: external_exports.array(external_exports.string()),
14624
+ createdAt: external_exports.string()
14625
+ }), external_exports.null()]),
14626
+ stats: external_exports.object({
14627
+ assets: external_exports.number(),
14628
+ findings: external_exports.number(),
14629
+ delegations: external_exports.number()
14630
+ }),
14631
+ stages: external_exports.array(external_exports.object({
14632
+ id: external_exports.string(),
14633
+ name: external_exports.string(),
14634
+ shortName: external_exports.string(),
14635
+ status: external_exports.string(),
14636
+ delegationCount: external_exports.number(),
14637
+ activeDelegationCount: external_exports.number()
14638
+ })),
14639
+ delegations: external_exports.array(external_exports.object({
14640
+ id: external_exports.string(),
14641
+ stageId: external_exports.string(),
14642
+ agentId: external_exports.string(),
14643
+ sessionId: external_exports.string().optional(),
14644
+ objective: external_exports.string(),
14645
+ status: external_exports.string(),
14646
+ createdAt: external_exports.string(),
14647
+ finishedAt: external_exports.string().optional()
14648
+ })),
14649
+ findings: external_exports.array(external_exports.object({
14650
+ id: external_exports.string(),
14651
+ title: external_exports.string(),
14652
+ severity: external_exports.string(),
14653
+ status: external_exports.string().optional(),
14654
+ sourceStage: external_exports.string(),
14655
+ sourceDelegationId: external_exports.string(),
14656
+ detailFile: external_exports.string(),
14657
+ affectedAsset: external_exports.string().optional(),
14658
+ description: external_exports.string().optional(),
14659
+ updatedAt: external_exports.string()
14660
+ })),
14661
+ trace: external_exports.object({
14662
+ nodes: external_exports.array(external_exports.object({
14663
+ id: external_exports.string(),
14664
+ kind: external_exports.string(),
14665
+ label: external_exports.string(),
14666
+ status: external_exports.string().optional(),
14667
+ sublabel: external_exports.string().optional(),
14668
+ timestamp: external_exports.string().optional()
14669
+ })),
14670
+ edges: external_exports.array(external_exports.object({
14671
+ id: external_exports.string(),
14672
+ source: external_exports.string(),
14673
+ target: external_exports.string(),
14674
+ kind: external_exports.string(),
14675
+ label: external_exports.string().optional()
14676
+ }))
14677
+ }),
14678
+ targets: external_exports.array(external_exports.object({
14679
+ id: external_exports.string(),
14680
+ target: external_exports.string(),
14681
+ canonicalTarget: external_exports.string(),
14682
+ rootSessionId: external_exports.string().nullable(),
14683
+ status: external_exports.string(),
14684
+ currentStage: external_exports.string().nullable(),
14685
+ createdAt: external_exports.string()
14686
+ }))
14687
+ });
14688
+ var unboundResultSchema = external_exports.object({
14689
+ kind: external_exports.literal("unbound"),
14690
+ targets: external_exports.array(external_exports.object({
14691
+ id: external_exports.string(),
14692
+ target: external_exports.string(),
14693
+ canonicalTarget: external_exports.string(),
14694
+ rootSessionId: external_exports.string().nullable(),
14695
+ status: external_exports.string(),
14696
+ currentStage: external_exports.string().nullable(),
14697
+ createdAt: external_exports.string()
14698
+ }))
14699
+ });
14700
+ var outputFileResultSchema = external_exports.object({
14701
+ path: external_exports.string(),
14702
+ name: external_exports.string(),
14703
+ size: external_exports.number(),
14704
+ mtime: external_exports.string(),
14705
+ mime: external_exports.enum(["text", "markdown", "json", "image", "binary"]),
14706
+ content: external_exports.string().optional()
14707
+ });
14708
+ var outputTreeResultSchema = external_exports.object({
14709
+ targetId: external_exports.string(),
14710
+ target: external_exports.string(),
14711
+ current: external_exports.boolean(),
14712
+ children: external_exports.array(external_exports.unknown())
14713
+ });
14714
+ var PENTESTER_VIEW_INVOCATION = {
14715
+ id: "dsh-pentester#pentesterView/command",
14716
+ service: "pentesterView",
14717
+ namespace: "pentesterView",
14718
+ method: "command",
14719
+ invocation: { kind: "direct" },
14720
+ cancellation: {
14721
+ parameter: "signal"
14722
+ },
14723
+ parameters: [
14724
+ {
14725
+ name: "input",
14726
+ wire: "input",
14727
+ source: "json",
14728
+ codec: {
14729
+ mode: "strict",
14730
+ typeSymbol: "dsh-pentester#PentesterViewInput",
14731
+ schema: pentesterViewInputSchema
14732
+ }
14733
+ }
14734
+ ],
14735
+ result: {
14736
+ mode: "strict",
14737
+ typeSymbol: "dsh-pentester#PentesterViewResult",
14738
+ schema: external_exports.object({
14739
+ ok: external_exports.boolean(),
14740
+ value: external_exports.union([snapshotResultSchema, outputTreeResultSchema, outputFileResultSchema, unboundResultSchema, external_exports.object({ ready: external_exports.literal(true), buildId: external_exports.string() })]).optional(),
14741
+ error: external_exports.string().optional(),
14742
+ code: external_exports.string().optional()
14743
+ })
14744
+ }
14745
+ };
14746
+ var PENTESTER_VIEW_REMOTE = {
14747
+ package: "dsh-pentester",
14748
+ descriptors: [PENTESTER_VIEW_INVOCATION]
14749
+ };
14750
+
14594
14751
  // src/ui/client/remote.ts
14595
14752
  function dockerCommand(face, input2) {
14596
14753
  if (face === void 0 || typeof face.command !== "function") {
@@ -14605,6 +14762,86 @@ function dockerCommand(face, input2) {
14605
14762
  return { ok: false, error: inner.error, code: inner.code };
14606
14763
  });
14607
14764
  }
14765
+ var pentesterEndpointState = "unknown";
14766
+ function getPentesterEndpointState() {
14767
+ return pentesterEndpointState;
14768
+ }
14769
+ function resetPentesterEndpointState() {
14770
+ if (pentesterEndpointState === "missing") {
14771
+ pentesterEndpointState = "unknown";
14772
+ }
14773
+ }
14774
+ function pentesterViewCommand(face, input2, timeoutMs = 3e4) {
14775
+ if (face === void 0 || typeof face.command !== "function") {
14776
+ return Promise.resolve({ ok: false, error: "rpc_unavailable", code: "rpc_unavailable" });
14777
+ }
14778
+ if (pentesterEndpointState === "missing") {
14779
+ return Promise.resolve({
14780
+ ok: false,
14781
+ error: "Pentester RPC was never registered by the running Host. Check Host build and plugin activation.",
14782
+ code: "rpc_endpoint_missing"
14783
+ });
14784
+ }
14785
+ const controller = new AbortController();
14786
+ const signal = AbortSignal.any([
14787
+ controller.signal,
14788
+ AbortSignal.timeout(timeoutMs)
14789
+ ]);
14790
+ const promise2 = face.command(input2, signal).then((result) => {
14791
+ pentesterEndpointState = "healthy";
14792
+ if (!result.ok) {
14793
+ const code = result.error.code;
14794
+ if (code === "transport" || result.error.message?.includes("404")) {
14795
+ pentesterEndpointState = "missing";
14796
+ return {
14797
+ ok: false,
14798
+ error: "Pentester RPC was never registered by the running Host.",
14799
+ code: "rpc_endpoint_missing"
14800
+ };
14801
+ }
14802
+ return { ok: false, error: result.error.message, code: result.error.code };
14803
+ }
14804
+ const inner = result.value;
14805
+ if (inner.ok) return { ok: true, value: inner.value };
14806
+ return { ok: false, error: inner.error, code: inner.code };
14807
+ }).catch((err) => {
14808
+ return {
14809
+ ok: false,
14810
+ error: err instanceof Error ? err.message : "network error",
14811
+ code: "network_error"
14812
+ };
14813
+ });
14814
+ return new Promise((resolve, reject) => {
14815
+ const onAbort = () => {
14816
+ if (signal.aborted) {
14817
+ if (signal.reason instanceof DOMException && signal.reason.name === "TimeoutError") {
14818
+ resolve({
14819
+ ok: false,
14820
+ error: `Pentester RPC timed out after ${timeoutMs}ms.`,
14821
+ code: "timeout"
14822
+ });
14823
+ } else {
14824
+ resolve({
14825
+ ok: false,
14826
+ error: "Pentester RPC request was aborted.",
14827
+ code: "aborted"
14828
+ });
14829
+ }
14830
+ }
14831
+ };
14832
+ signal.addEventListener("abort", onAbort, { once: true });
14833
+ promise2.then(
14834
+ (value) => {
14835
+ signal.removeEventListener("abort", onAbort);
14836
+ resolve(value);
14837
+ },
14838
+ (error51) => {
14839
+ signal.removeEventListener("abort", onAbort);
14840
+ reject(error51);
14841
+ }
14842
+ );
14843
+ });
14844
+ }
14608
14845
 
14609
14846
  // src/ui/client/pentester-config-section.tsx
14610
14847
  var import_react3 = require("react");
@@ -15160,61 +15397,3481 @@ function isPentesterRootSession(snapshot, id) {
15160
15397
  }
15161
15398
 
15162
15399
  // src/ui/client/pentester-view.tsx
15163
- var import_jsx_runtime4 = require("react/jsx-runtime");
15164
- function PentesterView() {
15165
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-testid": "pentester-view", "data-pentester-view": "" });
15400
+ var import_react18 = __toESM(require("react"), 1);
15401
+
15402
+ // src/ui/client/pentester/navigation.ts
15403
+ var PENTESTER_TABS = [
15404
+ { id: "overview", localeKey: "view.overview" },
15405
+ { id: "stages", localeKey: "view.stages" },
15406
+ { id: "trace", localeKey: "view.trace" },
15407
+ { id: "findings", localeKey: "view.findings" },
15408
+ { id: "output", localeKey: "view.output" }
15409
+ ];
15410
+
15411
+ // src/ui/client/pentester/PentesterTabBar.tsx
15412
+ var import_react4 = __toESM(require("react"), 1);
15413
+ function PentesterTabBar(props) {
15414
+ const { items, active, onSelect, variant = "primary" } = props;
15415
+ const gap = variant === "compact" ? 28 : 36;
15416
+ return import_react4.default.createElement(
15417
+ "div",
15418
+ {
15419
+ role: "tablist",
15420
+ style: {
15421
+ display: "flex",
15422
+ gap: `${gap}px`,
15423
+ paddingLeft: "8px",
15424
+ flexShrink: 0
15425
+ }
15426
+ },
15427
+ ...items.map(
15428
+ (item) => import_react4.default.createElement(
15429
+ "button",
15430
+ {
15431
+ key: item.id,
15432
+ role: "tab",
15433
+ "aria-selected": item.id === active,
15434
+ type: "button",
15435
+ onClick: () => onSelect(item.id),
15436
+ style: {
15437
+ position: "relative",
15438
+ padding: "0 0 11px",
15439
+ border: "none",
15440
+ background: "transparent",
15441
+ fontSize: "13px",
15442
+ lineHeight: "16px",
15443
+ fontWeight: 500,
15444
+ color: item.id === active ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-label-tertiary)",
15445
+ cursor: "pointer",
15446
+ whiteSpace: "nowrap"
15447
+ }
15448
+ },
15449
+ item.label,
15450
+ // Active underline pseudo-element rendered as a real element
15451
+ // because inline styles can't do ::after.
15452
+ item.id === active && import_react4.default.createElement("span", {
15453
+ "aria-hidden": true,
15454
+ style: {
15455
+ position: "absolute",
15456
+ right: 0,
15457
+ bottom: "1px",
15458
+ left: 0,
15459
+ height: "2px",
15460
+ borderRadius: "2px",
15461
+ background: "var(--dsw-alias-state-business-primary)"
15462
+ }
15463
+ })
15464
+ )
15465
+ )
15466
+ );
15166
15467
  }
15167
15468
 
15168
- // src/ui/client/plugin.tsx
15169
- var import_jsx_runtime5 = require("react/jsx-runtime");
15170
- var inject = ["slots", "remote", "sessions"];
15171
- function apply(ctx) {
15172
- ctx.effect(() => {
15173
- let disposeMount;
15174
- let disposed = false;
15175
- void ctx.remote.$mount(PENTESTER_REMOTE).then((dispose) => {
15176
- if (disposed) {
15177
- dispose();
15178
- return;
15469
+ // src/ui/client/pentester/OverviewView.tsx
15470
+ var import_react8 = __toESM(require("react"), 1);
15471
+
15472
+ // src/ui/client/pentester/MetricStrip.tsx
15473
+ var import_react5 = __toESM(require("react"), 1);
15474
+ function MetricStrip(props) {
15475
+ const { items } = props;
15476
+ return import_react5.default.createElement(
15477
+ "div",
15478
+ {
15479
+ style: {
15480
+ display: "flex",
15481
+ alignItems: "center",
15482
+ border: "1px solid var(--dsw-alias-border-l2)",
15483
+ borderRadius: "8px",
15484
+ background: "var(--dsw-alias-bg-base)",
15485
+ overflow: "hidden"
15179
15486
  }
15180
- disposeMount = dispose;
15181
- });
15182
- return () => {
15183
- disposed = true;
15184
- disposeMount?.();
15185
- };
15186
- }, "pentester: docker engine remote");
15187
- ctx.slots.inject(
15188
- "settings.section",
15189
- () => ctx.slots.register(
15190
- {
15191
- name: "settings.section",
15192
- id: "pentester-docker-engine",
15193
- order: 90,
15194
- label: () => "Pentester",
15195
- inject: () => {
15196
- const face = ctx.reflect?.get("remote.pentesterDocker");
15197
- return { command: (input2) => dockerCommand(face, input2) };
15198
- }
15487
+ },
15488
+ ...items.map(
15489
+ (item, i) => import_react5.default.createElement(
15490
+ import_react5.default.Fragment,
15491
+ { key: item.label },
15492
+ i > 0 && import_react5.default.createElement("div", {
15493
+ "aria-hidden": true,
15494
+ style: {
15495
+ width: "1px",
15496
+ height: "32px",
15497
+ background: "var(--dsw-alias-border-l2)",
15498
+ flexShrink: 0
15499
+ }
15500
+ }),
15501
+ import_react5.default.createElement(
15502
+ "div",
15503
+ {
15504
+ style: {
15505
+ flex: 1,
15506
+ display: "flex",
15507
+ flexDirection: "column",
15508
+ alignItems: "center",
15509
+ padding: "12px 16px",
15510
+ minWidth: "80px"
15511
+ }
15512
+ },
15513
+ import_react5.default.createElement("div", {
15514
+ style: {
15515
+ fontSize: "20px",
15516
+ fontWeight: 600,
15517
+ color: "var(--dsw-alias-label-primary)",
15518
+ lineHeight: "1.3"
15519
+ }
15520
+ }, item.value),
15521
+ import_react5.default.createElement("div", {
15522
+ style: {
15523
+ fontSize: "11px",
15524
+ color: "var(--dsw-alias-label-tertiary)",
15525
+ marginTop: "2px"
15526
+ }
15527
+ }, item.label)
15528
+ )
15529
+ )
15530
+ )
15531
+ );
15532
+ }
15533
+
15534
+ // src/ui/client/pentester/StageRail.tsx
15535
+ var import_react6 = __toESM(require("react"), 1);
15536
+
15537
+ // src/ui/client/pentester/status.ts
15538
+ function stageStatusToUi(status) {
15539
+ switch (status) {
15540
+ case "completed":
15541
+ return "completed";
15542
+ case "active":
15543
+ return "active";
15544
+ case "reviewing":
15545
+ return "reviewing";
15546
+ default:
15547
+ return "pending";
15548
+ }
15549
+ }
15550
+ function uiStatusSymbol(ui) {
15551
+ switch (ui) {
15552
+ case "completed":
15553
+ return "\u2713";
15554
+ case "active":
15555
+ return "\u25CF";
15556
+ case "reviewing":
15557
+ return "\u25D0";
15558
+ case "pending":
15559
+ return "\u25CB";
15560
+ case "blocked":
15561
+ return "!";
15562
+ case "failed":
15563
+ return "\xD7";
15564
+ }
15565
+ }
15566
+ function uiStatusColor(ui) {
15567
+ switch (ui) {
15568
+ case "completed":
15569
+ return "var(--dsw-status-done, #22c55e)";
15570
+ case "active":
15571
+ return "var(--dsw-status-ongoing, #3b82f6)";
15572
+ case "reviewing":
15573
+ return "var(--dsw-status-ongoing, #f59e0b)";
15574
+ case "pending":
15575
+ return "var(--dsw-fg-muted, #6b7280)";
15576
+ case "blocked":
15577
+ return "var(--dsw-status-warning, #f59e0b)";
15578
+ case "failed":
15579
+ return "var(--dsw-status-error, #ef4444)";
15580
+ }
15581
+ }
15582
+
15583
+ // src/ui/client/pentester/StageRail.tsx
15584
+ function StageRail(props) {
15585
+ const { stages, currentStage, onStageClick } = props;
15586
+ return import_react6.default.createElement(
15587
+ "div",
15588
+ {
15589
+ style: {
15590
+ display: "flex",
15591
+ alignItems: "center",
15592
+ gap: "0px",
15593
+ fontSize: "13px",
15594
+ lineHeight: "20px",
15595
+ overflowX: "auto",
15596
+ paddingBottom: "4px"
15199
15597
  },
15200
- (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PentesterConfigSection, { ...props })
15598
+ "aria-label": "PTES stages timeline"
15599
+ },
15600
+ ...stages.map((stage, i) => {
15601
+ const ui = stageStatusToUi(stage.status);
15602
+ const isCurrent = stage.id === currentStage;
15603
+ const symbol2 = uiStatusSymbol(ui);
15604
+ const color = uiStatusColor(ui);
15605
+ return import_react6.default.createElement(
15606
+ import_react6.default.Fragment,
15607
+ { key: stage.id },
15608
+ // Connector line between stages
15609
+ i > 0 && import_react6.default.createElement("span", {
15610
+ "aria-hidden": true,
15611
+ style: {
15612
+ width: "24px",
15613
+ height: "1px",
15614
+ background: "var(--dsw-alias-border-l2)",
15615
+ flexShrink: 0,
15616
+ margin: "0 0px"
15617
+ }
15618
+ }),
15619
+ import_react6.default.createElement(
15620
+ "button",
15621
+ {
15622
+ type: "button",
15623
+ title: `${stage.name} (${stage.status})`,
15624
+ onClick: onStageClick !== void 0 ? () => onStageClick(stage.id) : void 0,
15625
+ style: {
15626
+ display: "inline-flex",
15627
+ alignItems: "center",
15628
+ gap: "4px",
15629
+ padding: "2px 4px",
15630
+ border: "none",
15631
+ borderRadius: "4px",
15632
+ background: isCurrent ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
15633
+ color: isCurrent ? color : "var(--dsw-alias-label-secondary)",
15634
+ fontWeight: isCurrent ? 600 : 400,
15635
+ cursor: onStageClick !== void 0 ? "pointer" : "default",
15636
+ fontSize: "13px",
15637
+ lineHeight: "20px",
15638
+ whiteSpace: "nowrap",
15639
+ fontFamily: "inherit"
15640
+ }
15641
+ },
15642
+ import_react6.default.createElement("span", {
15643
+ style: { fontSize: "14px", fontWeight: isCurrent ? 700 : 400 }
15644
+ }, symbol2),
15645
+ import_react6.default.createElement("span", null, stage.shortName)
15646
+ )
15647
+ );
15648
+ })
15649
+ );
15650
+ }
15651
+
15652
+ // src/ui/client/pentester/DelegationActivityRow.tsx
15653
+ var import_react7 = __toESM(require("react"), 1);
15654
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
15655
+ function DelegationActivityRow(props) {
15656
+ const { delegation, stageShortName, onOpenSession, compact = false, liveActivity } = props;
15657
+ const hasSession = delegation.sessionId !== void 0;
15658
+ const dotState = (() => {
15659
+ if (delegation.status === "failed") return "error";
15660
+ if (delegation.status === "closed") return "done";
15661
+ if (liveActivity === "running") return "ongoing";
15662
+ if (liveActivity === "inactive") return "done";
15663
+ if (delegation.status === "starting") return "ongoing";
15664
+ return "done";
15665
+ })();
15666
+ return import_react7.default.createElement(
15667
+ "div",
15668
+ {
15669
+ role: hasSession ? "button" : void 0,
15670
+ tabIndex: hasSession ? 0 : void 0,
15671
+ "aria-label": hasSession ? `Open session ${delegation.id}` : void 0,
15672
+ title: delegation.objective,
15673
+ onClick: hasSession ? () => {
15674
+ if (delegation.sessionId !== void 0) onOpenSession(delegation.sessionId);
15675
+ } : void 0,
15676
+ onKeyDown: hasSession ? (e) => {
15677
+ if (e.key === "Enter" || e.key === " ") {
15678
+ e.preventDefault();
15679
+ if (delegation.sessionId !== void 0) onOpenSession(delegation.sessionId);
15680
+ }
15681
+ } : void 0,
15682
+ style: {
15683
+ display: "flex",
15684
+ alignItems: "center",
15685
+ gap: "8px",
15686
+ padding: compact ? "4px 8px" : "8px 12px",
15687
+ borderRadius: "6px",
15688
+ background: "var(--dsw-alias-bg-base)",
15689
+ border: "1px solid var(--dsw-alias-border-l2)",
15690
+ cursor: hasSession ? "pointer" : "default",
15691
+ fontSize: "13px",
15692
+ lineHeight: "20px"
15693
+ }
15694
+ },
15695
+ import_react7.default.createElement(import_dsh_client_ui_primitives2.StateDot, { state: dotState, size: 10 }),
15696
+ import_react7.default.createElement("span", {
15697
+ style: {
15698
+ fontWeight: 600,
15699
+ color: "var(--dsw-alias-label-primary)",
15700
+ flexShrink: 0,
15701
+ minWidth: compact ? "50px" : "60px"
15702
+ }
15703
+ }, `${delegation.id} \xB7 ${delegation.agentId}`),
15704
+ !compact && import_react7.default.createElement("span", {
15705
+ style: {
15706
+ color: "var(--dsw-alias-label-secondary)",
15707
+ flex: 1,
15708
+ overflow: "hidden",
15709
+ textOverflow: "ellipsis",
15710
+ whiteSpace: "nowrap",
15711
+ minWidth: 0
15712
+ }
15713
+ }, delegation.objective),
15714
+ !compact && stageShortName !== void 0 && import_react7.default.createElement("span", {
15715
+ style: {
15716
+ fontSize: "11px",
15717
+ color: "var(--dsw-alias-label-tertiary)",
15718
+ flexShrink: 0
15719
+ }
15720
+ }, stageShortName)
15721
+ );
15722
+ }
15723
+
15724
+ // src/ui/client/pentester/OverviewView.tsx
15725
+ var CARD_SURFACE = {
15726
+ border: "1px solid var(--dsw-alias-border-l2)",
15727
+ borderRadius: "8px",
15728
+ background: "var(--dsw-alias-bg-base)",
15729
+ padding: "16px",
15730
+ marginBottom: "16px"
15731
+ };
15732
+ function OverviewView(props) {
15733
+ const { snapshot, liveActivity, onNavigate, onOpenSession } = props;
15734
+ if (snapshot?.run === null || snapshot === null) {
15735
+ return import_react8.default.createElement("div", {
15736
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
15737
+ }, "No run data available.");
15738
+ }
15739
+ const run = snapshot.run;
15740
+ const stages = snapshot.stages;
15741
+ const currentStage = stages.find((s) => s.id === run.currentStage);
15742
+ const currentIndex = stages.findIndex((s) => s.id === run.currentStage);
15743
+ const allDelegations = snapshot.delegations;
15744
+ const currentWork = allDelegations.filter((d) => {
15745
+ const activity = liveActivity.get(d.id);
15746
+ return activity === "running" || d.status === "starting";
15747
+ });
15748
+ return import_react8.default.createElement(
15749
+ "div",
15750
+ { style: { maxWidth: "900px" } },
15751
+ // ── Target / Status / Current Stage card ──────────────────────────
15752
+ import_react8.default.createElement(
15753
+ "div",
15754
+ { style: CARD_SURFACE },
15755
+ import_react8.default.createElement(
15756
+ "div",
15757
+ {
15758
+ style: {
15759
+ display: "flex",
15760
+ alignItems: "center",
15761
+ justifyContent: "space-between",
15762
+ marginBottom: "12px"
15763
+ }
15764
+ },
15765
+ import_react8.default.createElement("div", {
15766
+ style: { fontSize: "16px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
15767
+ }, run.targets[0] ?? "No target"),
15768
+ import_react8.default.createElement("span", {
15769
+ style: {
15770
+ fontSize: "12px",
15771
+ padding: "2px 10px",
15772
+ borderRadius: "9999px",
15773
+ background: "var(--dsw-alias-interactive-bg-hover)",
15774
+ color: run.status === "active" ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-label-secondary)",
15775
+ border: "1px solid var(--dsw-alias-border-l2)",
15776
+ fontWeight: 500
15777
+ }
15778
+ }, run.status === "active" ? "Running" : "Completed")
15779
+ ),
15780
+ currentStage !== void 0 && import_react8.default.createElement(
15781
+ "div",
15782
+ {
15783
+ style: {
15784
+ display: "flex",
15785
+ alignItems: "baseline",
15786
+ gap: "8px",
15787
+ marginBottom: "12px"
15788
+ }
15789
+ },
15790
+ import_react8.default.createElement("span", {
15791
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary)" }
15792
+ }, "Current Stage"),
15793
+ import_react8.default.createElement("span", {
15794
+ style: { fontSize: "14px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
15795
+ }, currentStage.name),
15796
+ import_react8.default.createElement("span", {
15797
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary)" }
15798
+ }, `Stage ${currentIndex + 1} / ${stages.length}`)
15799
+ ),
15800
+ import_react8.default.createElement(StageRail, {
15801
+ stages,
15802
+ currentStage: run.currentStage,
15803
+ onStageClick: (stageId) => onNavigate("stages", { stage: stageId })
15804
+ })
15805
+ ),
15806
+ // ── Metrics strip (total delegations, not active) ─────────────────
15807
+ import_react8.default.createElement(MetricStrip, {
15808
+ items: [
15809
+ { label: "Assets", value: String(snapshot.stats.assets) },
15810
+ { label: "Findings", value: String(snapshot.stats.findings) },
15811
+ { label: "Delegations", value: String(snapshot.stats.delegations) }
15812
+ ]
15813
+ }),
15814
+ import_react8.default.createElement("div", { style: { height: "16px" } }),
15815
+ // ── Current Work (only running/starting) ──────────────────────────
15816
+ currentWork.length > 0 && import_react8.default.createElement(
15817
+ import_react8.default.Fragment,
15818
+ null,
15819
+ import_react8.default.createElement("div", {
15820
+ style: {
15821
+ fontSize: "13px",
15822
+ fontWeight: 600,
15823
+ color: "var(--dsw-alias-label-primary)",
15824
+ marginBottom: "8px"
15825
+ }
15826
+ }, "Current Work"),
15827
+ import_react8.default.createElement(
15828
+ "div",
15829
+ {
15830
+ style: { display: "flex", flexDirection: "column", gap: "4px" }
15831
+ },
15832
+ ...currentWork.map((d) => {
15833
+ const stage = stages.find((s) => s.id === d.stageId);
15834
+ return import_react8.default.createElement(DelegationActivityRow, {
15835
+ key: d.id,
15836
+ delegation: d,
15837
+ stageShortName: stage?.shortName,
15838
+ onOpenSession,
15839
+ liveActivity: liveActivity.get(d.id)
15840
+ });
15841
+ })
15842
+ )
15201
15843
  )
15202
15844
  );
15203
- ctx.slots.inject("conversation.view", () => {
15204
- let disposeEntry;
15205
- const sync = () => {
15206
- const snapshot = ctx.sessions.list.getSnapshot();
15207
- const current = snapshot.current;
15208
- if (current !== void 0 && isPentesterRootSession(snapshot, current)) {
15209
- if (disposeEntry === void 0) {
15210
- disposeEntry = ctx.slots.register(
15845
+ }
15846
+
15847
+ // src/ui/client/pentester/StagesView.tsx
15848
+ var import_react9 = __toESM(require("react"), 1);
15849
+ var STAGE_LABEL_WIDTH = 260;
15850
+ var MAX_WIDTH = 1100;
15851
+ var COMPACT_THRESHOLD = 720;
15852
+ var STATUS_TEXT = {
15853
+ completed: "Completed",
15854
+ active: "Active",
15855
+ reviewing: "Pending approval",
15856
+ pending: "Pending",
15857
+ blocked: "Blocked",
15858
+ failed: "Failed"
15859
+ };
15860
+ function StagesView(props) {
15861
+ const { snapshot, liveActivity, onOpenSession, focusStage } = props;
15862
+ if (snapshot === null || snapshot.run === null) {
15863
+ return import_react9.default.createElement("div", {
15864
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
15865
+ }, "No run data available.");
15866
+ }
15867
+ const stages = snapshot.stages;
15868
+ const allDelegations = snapshot.delegations;
15869
+ const delegationsByStage = /* @__PURE__ */ new Map();
15870
+ for (const d of allDelegations) {
15871
+ const existing = delegationsByStage.get(d.stageId) ?? [];
15872
+ delegationsByStage.set(d.stageId, [...existing, d]);
15873
+ }
15874
+ const containerRef = (0, import_react9.useRef)(null);
15875
+ const [compact, setCompact] = (0, import_react9.useState)(false);
15876
+ (0, import_react9.useEffect)(() => {
15877
+ const el = containerRef.current;
15878
+ if (el === null) return;
15879
+ const check2 = () => setCompact(el.clientHeight < COMPACT_THRESHOLD);
15880
+ check2();
15881
+ const obs = new ResizeObserver(check2);
15882
+ obs.observe(el);
15883
+ return () => obs.disconnect();
15884
+ }, []);
15885
+ const focusRef = (0, import_react9.useRef)(null);
15886
+ (0, import_react9.useEffect)(() => {
15887
+ if (focusStage !== void 0 && focusRef.current !== null) {
15888
+ focusRef.current.scrollIntoView({ behavior: "auto", block: "center" });
15889
+ }
15890
+ }, [focusStage]);
15891
+ const cardMb = compact ? 6 : 8;
15892
+ const emptyMinH = compact ? 64 : 72;
15893
+ const leftPad = compact ? 12 : 16;
15894
+ const leftPadH = compact ? 10 : 16;
15895
+ const rightPad = compact ? 8 : 12;
15896
+ const rightPadH = compact ? 10 : 14;
15897
+ const titleGap = compact ? 8 : 10;
15898
+ const titleSize = compact ? 13 : 14;
15899
+ const markerSize = compact ? 14 : 16;
15900
+ const statusMargin = compact ? 24 : 28;
15901
+ const delGap = compact ? 4 : 6;
15902
+ const legendGap = compact ? 14 : 18;
15903
+ const topPad = compact ? 4 : 8;
15904
+ const bottomPad = compact ? 12 : 16;
15905
+ return import_react9.default.createElement(
15906
+ "div",
15907
+ {
15908
+ ref: containerRef,
15909
+ style: {
15910
+ overflow: "visible",
15911
+ paddingBottom: `${bottomPad}px`,
15912
+ display: "flex",
15913
+ flexDirection: "column",
15914
+ alignItems: "center",
15915
+ minHeight: 0
15916
+ }
15917
+ },
15918
+ import_react9.default.createElement(
15919
+ "div",
15920
+ {
15921
+ style: {
15922
+ width: "100%",
15923
+ maxWidth: `${MAX_WIDTH}px`,
15924
+ position: "relative",
15925
+ paddingTop: `${topPad}px`
15926
+ }
15927
+ },
15928
+ // ── Continuous vertical backbone ──────────────────────────────────
15929
+ import_react9.default.createElement("div", {
15930
+ "aria-hidden": true,
15931
+ style: {
15932
+ position: "absolute",
15933
+ left: `${STAGE_LABEL_WIDTH + 10}px`,
15934
+ top: `${compact ? 16 : 24}px`,
15935
+ bottom: "12px",
15936
+ width: "1px",
15937
+ background: "var(--dsw-alias-border-l2)",
15938
+ zIndex: 0
15939
+ }
15940
+ }),
15941
+ ...stages.map((stage, i) => {
15942
+ const ui = stageStatusToUi(stage.status);
15943
+ const isCurrent = stage.id === snapshot.run.currentStage;
15944
+ const isFocus = stage.id === focusStage;
15945
+ const color = uiStatusColor(ui);
15946
+ const symbol2 = uiStatusSymbol(ui);
15947
+ const stageDelegations = delegationsByStage.get(stage.id) ?? [];
15948
+ const statusText = STATUS_TEXT[ui] ?? ui;
15949
+ return import_react9.default.createElement(
15950
+ "div",
15951
+ {
15952
+ key: stage.id,
15953
+ ref: isFocus ? focusRef : void 0,
15954
+ style: {
15955
+ display: "grid",
15956
+ gridTemplateColumns: `${STAGE_LABEL_WIDTH}px minmax(0, 1fr)`,
15957
+ minHeight: stageDelegations.length > 0 ? "auto" : `${emptyMinH}px`,
15958
+ border: "1px solid var(--dsw-alias-border-l2)",
15959
+ borderRadius: "8px",
15960
+ background: "var(--dsw-alias-bg-base)",
15961
+ marginBottom: `${cardMb}px`,
15962
+ position: "relative",
15963
+ zIndex: 1,
15964
+ overflow: "hidden"
15965
+ }
15966
+ },
15967
+ // ── Left column: marker + stage info ─────────────────────────
15968
+ import_react9.default.createElement(
15969
+ "div",
15211
15970
  {
15212
- name: "conversation.view",
15213
- id: "pentester",
15214
- order: 20,
15215
- label: "Pentester"
15971
+ style: {
15972
+ display: "flex",
15973
+ flexDirection: "column",
15974
+ justifyContent: "center",
15975
+ padding: `${leftPadH}px ${leftPad}px`,
15976
+ gap: `${compact ? 2 : 4}px`,
15977
+ background: "var(--dsw-alias-bg-base)",
15978
+ position: "relative",
15979
+ zIndex: 1
15980
+ }
15216
15981
  },
15217
- PentesterView
15982
+ import_react9.default.createElement(
15983
+ "div",
15984
+ {
15985
+ style: {
15986
+ display: "flex",
15987
+ alignItems: "center",
15988
+ gap: `${titleGap}px`
15989
+ }
15990
+ },
15991
+ import_react9.default.createElement("span", {
15992
+ style: {
15993
+ fontSize: `${markerSize}px`,
15994
+ color,
15995
+ fontWeight: isCurrent ? 700 : 400,
15996
+ lineHeight: "1",
15997
+ flexShrink: 0,
15998
+ width: `${compact ? 16 : 18}px`,
15999
+ textAlign: "center",
16000
+ background: "var(--dsw-alias-bg-base)",
16001
+ position: "relative",
16002
+ zIndex: 2
16003
+ }
16004
+ }, symbol2),
16005
+ import_react9.default.createElement("span", {
16006
+ style: {
16007
+ fontSize: `${titleSize}px`,
16008
+ fontWeight: isCurrent ? 600 : 500,
16009
+ color: "var(--dsw-alias-label-primary)",
16010
+ lineHeight: "20px"
16011
+ }
16012
+ }, stage.name)
16013
+ ),
16014
+ import_react9.default.createElement("span", {
16015
+ style: {
16016
+ fontSize: "12px",
16017
+ color: "var(--dsw-alias-label-tertiary)",
16018
+ marginLeft: `${statusMargin}px`,
16019
+ lineHeight: "16px"
16020
+ }
16021
+ }, statusText)
16022
+ ),
16023
+ // ── Right column: delegations ─────────────────────────────────
16024
+ import_react9.default.createElement(
16025
+ "div",
16026
+ {
16027
+ style: {
16028
+ padding: `${rightPadH}px ${rightPad}px`,
16029
+ display: "flex",
16030
+ flexDirection: "column",
16031
+ justifyContent: "center",
16032
+ gap: `${delGap}px`
16033
+ }
16034
+ },
16035
+ stageDelegations.length > 0 && import_react9.default.createElement("div", {
16036
+ style: {
16037
+ fontSize: "11px",
16038
+ color: "var(--dsw-alias-label-tertiary)",
16039
+ marginBottom: "1px"
16040
+ }
16041
+ }, `${stageDelegations.length} agent${stageDelegations.length !== 1 ? "s" : ""}`),
16042
+ ...stageDelegations.map(
16043
+ (d) => import_react9.default.createElement(
16044
+ "div",
16045
+ {
16046
+ key: d.id,
16047
+ style: {
16048
+ maxWidth: "min(100%, 480px)"
16049
+ }
16050
+ },
16051
+ import_react9.default.createElement(DelegationActivityRow, {
16052
+ delegation: d,
16053
+ onOpenSession,
16054
+ compact: true,
16055
+ liveActivity: liveActivity.get(d.id)
16056
+ })
16057
+ )
16058
+ ),
16059
+ stageDelegations.length === 0 && import_react9.default.createElement("span", {
16060
+ style: {
16061
+ fontSize: "12px",
16062
+ color: "var(--dsw-alias-label-tertiary)",
16063
+ fontStyle: "italic"
16064
+ }
16065
+ }, "No delegations")
16066
+ )
16067
+ );
16068
+ }),
16069
+ // ── Legend ─────────────────────────────────────────────────────────
16070
+ import_react9.default.createElement(
16071
+ "div",
16072
+ {
16073
+ style: {
16074
+ display: "flex",
16075
+ gap: `${legendGap}px`,
16076
+ padding: `${compact ? 6 : 10}px 0 ${compact ? 2 : 4}px 20px`,
16077
+ fontSize: "11px",
16078
+ color: "var(--dsw-alias-label-tertiary)"
16079
+ }
16080
+ },
16081
+ import_react9.default.createElement(LegendItem, { symbol: "\u2713", label: "Completed" }),
16082
+ import_react9.default.createElement(LegendItem, { symbol: "\u25CF", label: "Active" }),
16083
+ import_react9.default.createElement(LegendItem, { symbol: "\u25D0", label: "Pending approval" }),
16084
+ import_react9.default.createElement(LegendItem, { symbol: "\u25CB", label: "Pending" }),
16085
+ import_react9.default.createElement(LegendItem, { symbol: "!", label: "Blocked" }),
16086
+ import_react9.default.createElement(LegendItem, { symbol: "\xD7", label: "Failed" })
16087
+ )
16088
+ )
16089
+ );
16090
+ }
16091
+ function LegendItem(props) {
16092
+ return import_react9.default.createElement(
16093
+ "div",
16094
+ {
16095
+ style: { display: "flex", alignItems: "center", gap: "4px" }
16096
+ },
16097
+ import_react9.default.createElement("span", { style: { width: "14px", textAlign: "center" } }, props.symbol),
16098
+ import_react9.default.createElement("span", null, props.label)
16099
+ );
16100
+ }
16101
+
16102
+ // src/ui/client/pentester/FindingsView.tsx
16103
+ var import_react10 = __toESM(require("react"), 1);
16104
+ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
16105
+ var SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
16106
+ var SEVERITY_LABELS = {
16107
+ critical: "Critical",
16108
+ high: "High",
16109
+ medium: "Medium",
16110
+ low: "Low",
16111
+ info: "Info"
16112
+ };
16113
+ var SEVERITY_COLORS = {
16114
+ critical: "#ef4444",
16115
+ high: "#f97316",
16116
+ medium: "#eab308",
16117
+ low: "#3b82f6",
16118
+ info: "#6b7280"
16119
+ };
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
+ }
16150
+ 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);
16155
+ 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;
16166
+ }, [findings]);
16167
+ const openDetail = (0, import_react10.useCallback)(async (finding) => {
16168
+ setSelectedFinding(finding);
16169
+ setDetailContent(null);
16170
+ setDetailLoading(true);
16171
+ try {
16172
+ const result = await command({
16173
+ kind: "readFinding",
16174
+ detailFile: finding.detailFile,
16175
+ sessionId
16176
+ });
16177
+ if (result.ok) {
16178
+ const fileResult = result.value;
16179
+ setDetailContent(fileResult.content ?? "");
16180
+ } else {
16181
+ setDetailContent("*Unable to load finding detail.*");
16182
+ }
16183
+ } catch {
16184
+ setDetailContent("*Error loading finding detail.*");
16185
+ } finally {
16186
+ setDetailLoading(false);
16187
+ }
16188
+ }, [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(
16203
+ "div",
16204
+ {
16205
+ key: finding.id,
16206
+ style: {
16207
+ display: "flex",
16208
+ alignItems: "center",
16209
+ gap: "8px",
16210
+ padding: "6px 12px",
16211
+ cursor: "pointer",
16212
+ borderRadius: "4px",
16213
+ fontSize: "13px",
16214
+ color: "var(--dsw-fg)",
16215
+ borderLeft: `3px solid ${SEVERITY_COLORS[finding.severity]}`,
16216
+ marginBottom: "2px"
16217
+ },
16218
+ onClick: () => openDetail(finding)
16219
+ },
16220
+ import_react10.default.createElement("span", {
16221
+ style: {
16222
+ width: "8px",
16223
+ height: "8px",
16224
+ borderRadius: "50%",
16225
+ background: SEVERITY_COLORS[finding.severity],
16226
+ flexShrink: 0
16227
+ }
16228
+ }),
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,
16244
+ {
16245
+ open: true,
16246
+ onClose: closeDetail,
16247
+ title: selectedFinding.title,
16248
+ contentClassName: "pentester-finding-detail"
16249
+ },
16250
+ import_react10.default.createElement(
16251
+ "div",
16252
+ { style: { fontSize: "13px", lineHeight: "1.6" } },
16253
+ import_react10.default.createElement("div", {
16254
+ style: {
16255
+ display: "inline-block",
16256
+ padding: "2px 10px",
16257
+ borderRadius: "10px",
16258
+ background: SEVERITY_COLORS[selectedFinding.severity],
16259
+ color: "#fff",
16260
+ fontSize: "12px",
16261
+ fontWeight: 600,
16262
+ marginBottom: "12px"
16263
+ }
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(
16278
+ "div",
16279
+ { style: { maxWidth: "640px" } },
16280
+ ...severitySections,
16281
+ modalContent
16282
+ );
16283
+ }
16284
+
16285
+ // src/ui/client/pentester/TraceView.tsx
16286
+ var import_react15 = __toESM(require("react"), 1);
16287
+
16288
+ // src/ui/client/pentester/trace/TraceTimeline.tsx
16289
+ var import_react14 = __toESM(require("react"), 1);
16290
+
16291
+ // src/ui/client/pentester/trace/types.ts
16292
+ var STAGE_NODE_H = 72;
16293
+ var START_NODE_H = 48;
16294
+ var AGENT_NODE_W = 195;
16295
+ var AGENT_NODE_H = 68;
16296
+ var DELIVERY_NODE_W = 110;
16297
+ var DELIVERY_NODE_H = 62;
16298
+ var STAGE_TO_AGENT_GAP = 60;
16299
+ var AGENT_TO_DELIVERY_GAP = 50;
16300
+ var DELIVERY_ROW_GAP = 18;
16301
+ var STAGE_SECTION_PAD = 32;
16302
+ var TOP_OFFSET = 20;
16303
+ var MIN_SCALE = 0.5;
16304
+ var MAX_SCALE = 1.75;
16305
+ var DEFAULT_SCALE = 1;
16306
+ var AGENT_OFFSET = 280;
16307
+ var MAX_DELIVERY_PER_ROW = 3;
16308
+ var DELIVERY_H_GAP = 20;
16309
+ var TIME_PANEL_W = 245;
16310
+ var GRAPH_PADDING = 32;
16311
+ var TIMELINE_DOT_W = 16;
16312
+ var TIMELINE_TIME_W = 66;
16313
+ var TIMELINE_ROW_H = 44;
16314
+ var MIN_BAND_HEIGHT = 44;
16315
+ var BAND_GAP = 8;
16316
+ var COMPOSER_BOTTOM_PAD = 200;
16317
+ function formatTime(iso) {
16318
+ try {
16319
+ const d = new Date(iso);
16320
+ if (isNaN(d.getTime())) return "--";
16321
+ const hh = String(d.getHours()).padStart(2, "0");
16322
+ const mm = String(d.getMinutes()).padStart(2, "0");
16323
+ const ss = String(d.getSeconds()).padStart(2, "0");
16324
+ return `${hh}:${mm}:${ss}`;
16325
+ } catch {
16326
+ return "--";
16327
+ }
16328
+ }
16329
+
16330
+ // src/ui/client/pentester/trace/aggregation.ts
16331
+ var KIND_ORDER = ["result", "evidence", "artifact", "raw_output", "finding", "asset"];
16332
+ function aggregateDeliveries(snapshot) {
16333
+ const byDelegation = /* @__PURE__ */ new Map();
16334
+ const ensure = (delId) => {
16335
+ let m = byDelegation.get(delId);
16336
+ if (m === void 0) {
16337
+ m = /* @__PURE__ */ new Map();
16338
+ byDelegation.set(delId, m);
16339
+ }
16340
+ return m;
16341
+ };
16342
+ for (const f of snapshot.findings) {
16343
+ if (f.sourceDelegationId === "") continue;
16344
+ const delId = f.sourceDelegationId;
16345
+ const m = ensure(delId);
16346
+ let g = m.get("finding");
16347
+ if (g === void 0) {
16348
+ g = { names: [], navPath: f.id };
16349
+ m.set("finding", g);
16350
+ }
16351
+ g.names.push(f.title);
16352
+ }
16353
+ const result = /* @__PURE__ */ new Map();
16354
+ for (const [delId, kindMap] of byDelegation) {
16355
+ const groups = [];
16356
+ for (const kind of KIND_ORDER) {
16357
+ const g = kindMap.get(kind);
16358
+ if (g === void 0 || g.names.length === 0) continue;
16359
+ groups.push({
16360
+ kind,
16361
+ label: deliveryLabel(kind, g.names.length),
16362
+ count: g.names.length,
16363
+ navPath: g.navPath,
16364
+ navTab: kind === "finding" ? "findings" : "output",
16365
+ sampleNames: g.names.slice(0, 5)
16366
+ });
16367
+ }
16368
+ if (groups.length > 0) result.set(delId, groups);
16369
+ }
16370
+ return result;
16371
+ }
16372
+ function deliveryLabel(kind, count) {
16373
+ if (count === 1) {
16374
+ switch (kind) {
16375
+ case "result":
16376
+ return "Result";
16377
+ case "evidence":
16378
+ return "Evidence";
16379
+ case "artifact":
16380
+ return "Artifact";
16381
+ case "raw_output":
16382
+ return "Raw output";
16383
+ case "finding":
16384
+ return "Finding";
16385
+ case "asset":
16386
+ return "Asset";
16387
+ }
16388
+ }
16389
+ switch (kind) {
16390
+ case "result":
16391
+ return "Results";
16392
+ case "evidence":
16393
+ return "Evidence";
16394
+ case "artifact":
16395
+ return "Artifacts";
16396
+ case "raw_output":
16397
+ return "Raw outputs";
16398
+ case "finding":
16399
+ return "Findings";
16400
+ case "asset":
16401
+ return "Assets";
16402
+ }
16403
+ }
16404
+
16405
+ // src/ui/client/pentester/trace/layout.ts
16406
+ var PTES_STAGE_IDS = [
16407
+ "pre-engagement",
16408
+ "intelligence-gathering",
16409
+ "threat-modeling",
16410
+ "vulnerability-analysis",
16411
+ "exploitation",
16412
+ "post-exploitation",
16413
+ "reporting"
16414
+ ];
16415
+ var STAGE_SHORT = {
16416
+ "pre-engagement": "Pre",
16417
+ "intelligence-gathering": "Intel",
16418
+ "threat-modeling": "Threat",
16419
+ "vulnerability-analysis": "Vuln",
16420
+ "exploitation": "Exploit",
16421
+ "post-exploitation": "Post",
16422
+ "reporting": "Report"
16423
+ };
16424
+ var STAGE_FULL = {
16425
+ "pre-engagement": "Pre-engagement",
16426
+ "intelligence-gathering": "Intelligence Gathering",
16427
+ "threat-modeling": "Threat Modeling",
16428
+ "vulnerability-analysis": "Vulnerability Analysis",
16429
+ "exploitation": "Exploitation",
16430
+ "post-exploitation": "Post Exploitation",
16431
+ "reporting": "Reporting"
16432
+ };
16433
+ var MIN_WORLD_HALF = 600;
16434
+ function layoutDeliveryRows(groups, agentX) {
16435
+ if (groups.length === 0) return { rows: [], rowCount: 0 };
16436
+ const rows = [];
16437
+ for (let i = 0; i < groups.length; i += MAX_DELIVERY_PER_ROW) {
16438
+ const rowGroup = groups.slice(i, i + MAX_DELIVERY_PER_ROW);
16439
+ const totalW = rowGroup.length * DELIVERY_NODE_W + (rowGroup.length - 1) * DELIVERY_H_GAP;
16440
+ const startX = agentX - totalW / 2 + DELIVERY_NODE_W / 2;
16441
+ const row3 = [];
16442
+ for (let gi = 0; gi < rowGroup.length; gi++) {
16443
+ row3.push(startX + gi * (DELIVERY_NODE_W + DELIVERY_H_GAP));
16444
+ }
16445
+ rows.push(row3);
16446
+ }
16447
+ return { rows, rowCount: rows.length };
16448
+ }
16449
+ function buildTraceLayout(snapshot) {
16450
+ const run = snapshot.run;
16451
+ const stageViews = snapshot.stages;
16452
+ const delegations = snapshot.delegations;
16453
+ const deliveryMap = aggregateDeliveries(snapshot);
16454
+ const sections = [];
16455
+ const timeBands = [];
16456
+ let cursorY = TOP_OFFSET;
16457
+ const START_GAP = 20;
16458
+ sections.push({
16459
+ stageId: "start",
16460
+ label: "Start",
16461
+ sublabel: "Pentest begins",
16462
+ status: "completed",
16463
+ timestamp: run.createdAt,
16464
+ y: cursorY,
16465
+ height: START_NODE_H + START_GAP,
16466
+ agentBranches: [],
16467
+ isFuture: false
16468
+ });
16469
+ timeBands.push({
16470
+ id: "band:start",
16471
+ timestamp: run.createdAt,
16472
+ worldY: cursorY,
16473
+ sectionIndex: 0,
16474
+ timelineItems: [{
16475
+ timeLabel: formatTime(run.createdAt),
16476
+ title: "Start",
16477
+ subtitle: "Assessment started",
16478
+ kind: "stage",
16479
+ dotColor: "var(--dsw-status-done, #22c55e)"
16480
+ }]
16481
+ });
16482
+ cursorY += START_NODE_H + START_GAP;
16483
+ for (let sectionIndex = 0; sectionIndex < PTES_STAGE_IDS.length; sectionIndex++) {
16484
+ const stageId = PTES_STAGE_IDS[sectionIndex];
16485
+ const stageView = stageViews.find((s) => s.id === stageId);
16486
+ const stageName = STAGE_SHORT[stageId] ?? stageId;
16487
+ const stageFullName = STAGE_FULL[stageId] ?? stageId;
16488
+ const stageStatus = stageView?.status ?? "pending";
16489
+ const isCurrent = run.currentStage === stageId;
16490
+ const traceNodes = snapshot.trace.nodes;
16491
+ const stageNode = traceNodes.find((n) => n.id === `stage:${stageId}`);
16492
+ const stageTimestamp = stageNode?.timestamp;
16493
+ const isFuture = stageTimestamp === void 0 && !isCurrent && stageStatus === "pending";
16494
+ const stageDelegations = delegations.filter((d) => d.stageId === stageId);
16495
+ const agentBranches = [];
16496
+ const timelineItems = [];
16497
+ if (stageDelegations.length > 0) {
16498
+ const agentY = cursorY + STAGE_NODE_H + STAGE_TO_AGENT_GAP;
16499
+ for (let i = 0; i < stageDelegations.length; i++) {
16500
+ const d = stageDelegations[i];
16501
+ const dir = i % 2 === 0 ? -1 : 1;
16502
+ const pairIndex = Math.floor(i / 2);
16503
+ const offset = dir * (AGENT_OFFSET + pairIndex * 60);
16504
+ const agentX = offset;
16505
+ const groups = deliveryMap.get(d.id) ?? [];
16506
+ const { rows: deliveryRows, rowCount: deliveryRowCount } = layoutDeliveryRows(groups, agentX);
16507
+ const deliveryY = agentY + AGENT_NODE_H + AGENT_TO_DELIVERY_GAP;
16508
+ agentBranches.push({
16509
+ delegationId: d.id,
16510
+ agentId: d.agentId,
16511
+ label: `${d.id} \xB7 ${d.agentId}`,
16512
+ status: d.status,
16513
+ sessionId: d.sessionId,
16514
+ timestamp: d.createdAt,
16515
+ x: agentX,
16516
+ y: agentY,
16517
+ deliveryGroups: groups,
16518
+ deliveryRows,
16519
+ deliveryY: groups.length > 0 ? deliveryY : agentY + AGENT_NODE_H,
16520
+ deliveryRowCount
16521
+ });
16522
+ }
16523
+ }
16524
+ let sectionBottom = cursorY + STAGE_NODE_H;
16525
+ if (agentBranches.length > 0) {
16526
+ const maxDelBottom = Math.max(...agentBranches.map((a) => {
16527
+ if (a.deliveryGroups.length > 0) {
16528
+ return a.deliveryY + a.deliveryRowCount * (DELIVERY_NODE_H + DELIVERY_ROW_GAP) - DELIVERY_ROW_GAP;
16529
+ }
16530
+ return a.y + AGENT_NODE_H;
16531
+ }));
16532
+ sectionBottom = Math.max(sectionBottom, maxDelBottom);
16533
+ }
16534
+ sectionBottom += STAGE_SECTION_PAD;
16535
+ const contentHeight = sectionBottom - cursorY;
16536
+ if (isFuture) continue;
16537
+ const sectionIdx = sections.length;
16538
+ sections.push({
16539
+ stageId,
16540
+ label: stageName,
16541
+ sublabel: stageFullName,
16542
+ status: stageStatus,
16543
+ timestamp: stageTimestamp,
16544
+ y: cursorY,
16545
+ height: contentHeight,
16546
+ agentBranches,
16547
+ isFuture: false
16548
+ });
16549
+ if (stageTimestamp !== void 0) {
16550
+ timelineItems.push({
16551
+ timeLabel: formatTime(stageTimestamp),
16552
+ title: stageName,
16553
+ subtitle: stageStatus === "completed" ? "Stage completed" : "Stage started",
16554
+ kind: "stage",
16555
+ dotColor: stageStatus === "completed" ? "var(--dsw-status-done)" : stageStatus === "active" ? "var(--dsw-alias-state-business-primary)" : void 0
16556
+ });
16557
+ }
16558
+ const agentTimestamps = /* @__PURE__ */ new Map();
16559
+ for (const a of agentBranches) {
16560
+ if (a.timestamp !== void 0) {
16561
+ const key = a.timestamp;
16562
+ let list = agentTimestamps.get(key);
16563
+ if (list === void 0) {
16564
+ list = [];
16565
+ agentTimestamps.set(key, list);
16566
+ }
16567
+ list.push(a);
16568
+ }
16569
+ }
16570
+ for (const [ts, agents] of agentTimestamps) {
16571
+ if (agents.length === 1) {
16572
+ const a = agents[0];
16573
+ timelineItems.push({
16574
+ timeLabel: formatTime(ts),
16575
+ title: a.label,
16576
+ subtitle: a.status === "starting" ? "Agent starting" : a.status === "active" ? "Agent active" : "Agent started",
16577
+ kind: "delegation"
16578
+ });
16579
+ } else {
16580
+ for (const a of agents) {
16581
+ timelineItems.push({
16582
+ timeLabel: formatTime(ts),
16583
+ title: a.label,
16584
+ subtitle: a.status === "starting" ? "Agent starting" : "Agent started",
16585
+ kind: "delegation"
16586
+ });
16587
+ }
16588
+ }
16589
+ }
16590
+ timeBands.push({
16591
+ id: `band:${stageId}`,
16592
+ timestamp: stageTimestamp,
16593
+ worldY: cursorY,
16594
+ sectionIndex: sectionIdx,
16595
+ timelineItems
16596
+ });
16597
+ cursorY = sectionBottom;
16598
+ }
16599
+ let maxExtent = MIN_WORLD_HALF;
16600
+ for (const s of sections) {
16601
+ for (const a of s.agentBranches) {
16602
+ const absX = Math.abs(a.x);
16603
+ if (absX + AGENT_NODE_W / 2 > maxExtent) maxExtent = absX + AGENT_NODE_W / 2;
16604
+ for (const row3 of a.deliveryRows) {
16605
+ for (const dx of row3) {
16606
+ if (Math.abs(dx) + DELIVERY_NODE_W / 2 > maxExtent) maxExtent = Math.abs(dx) + DELIVERY_NODE_W / 2;
16607
+ }
16608
+ }
16609
+ }
16610
+ }
16611
+ maxExtent += 80;
16612
+ const worldHalfWidth = maxExtent;
16613
+ const worldHeight = cursorY + 80;
16614
+ return {
16615
+ sections,
16616
+ timeBands,
16617
+ worldHalfWidth,
16618
+ worldHeight,
16619
+ spineX: worldHalfWidth
16620
+ };
16621
+ }
16622
+
16623
+ // src/ui/client/pentester/trace/scaledLayout.ts
16624
+ function computeTimelineHeight(items) {
16625
+ if (items.length === 0) return 0;
16626
+ return 18 + items.length * TIMELINE_ROW_H + 8;
16627
+ }
16628
+ function computeTopologyHeight(section, scale) {
16629
+ if (section.stageId === "start") {
16630
+ return (START_NODE_H + 20) * scale;
16631
+ }
16632
+ let h = STAGE_NODE_H;
16633
+ if (section.agentBranches.length > 0) {
16634
+ h += STAGE_TO_AGENT_GAP + AGENT_NODE_H;
16635
+ const maxDelRows = Math.max(...section.agentBranches.map((a) => a.deliveryRowCount));
16636
+ if (maxDelRows > 0) {
16637
+ h += AGENT_TO_DELIVERY_GAP + maxDelRows * (DELIVERY_NODE_H + DELIVERY_ROW_GAP) - DELIVERY_ROW_GAP;
16638
+ }
16639
+ }
16640
+ h += STAGE_SECTION_PAD;
16641
+ return h * scale;
16642
+ }
16643
+ function adjustSectionY(section, deltaY) {
16644
+ const adjustAgentY = (a) => ({
16645
+ ...a,
16646
+ y: a.y + deltaY,
16647
+ deliveryY: a.deliveryY + deltaY
16648
+ });
16649
+ return {
16650
+ ...section,
16651
+ y: section.y + deltaY,
16652
+ agentBranches: section.agentBranches.map(adjustAgentY)
16653
+ };
16654
+ }
16655
+ function buildScaledTraceLayout(baseLayout, scale) {
16656
+ const bands = [];
16657
+ let visualY = 20;
16658
+ for (let i = 0; i < baseLayout.timeBands.length; i++) {
16659
+ const band = baseLayout.timeBands[i];
16660
+ const section = baseLayout.sections[band.sectionIndex];
16661
+ const timelineH = computeTimelineHeight(band.timelineItems);
16662
+ const topologyH = computeTopologyHeight(section, scale);
16663
+ const visualH = Math.max(timelineH, topologyH, MIN_BAND_HEIGHT);
16664
+ const adjustedWorldY = visualY / scale;
16665
+ const deltaY = adjustedWorldY - section.y;
16666
+ bands.push({
16667
+ bandId: band.id,
16668
+ visualY,
16669
+ visualHeight: visualH,
16670
+ sectionIndex: band.sectionIndex,
16671
+ adjustedWorldY,
16672
+ timelineItems: band.timelineItems
16673
+ });
16674
+ visualY += visualH + BAND_GAP;
16675
+ }
16676
+ const adjustedSections = baseLayout.sections.map((section, idx) => {
16677
+ const band = bands.find((b) => b.sectionIndex === idx);
16678
+ if (band === void 0) return section;
16679
+ const deltaY = band.adjustedWorldY - section.y;
16680
+ return deltaY === 0 ? section : adjustSectionY(section, deltaY);
16681
+ });
16682
+ const totalHeight = visualY + COMPOSER_BOTTOM_PAD;
16683
+ return {
16684
+ bands,
16685
+ totalHeight,
16686
+ worldHalfWidth: baseLayout.worldHalfWidth,
16687
+ spineX: baseLayout.spineX,
16688
+ sections: adjustedSections
16689
+ };
16690
+ }
16691
+
16692
+ // 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");
16695
+ var CARD = {
16696
+ position: "absolute",
16697
+ borderRadius: "7px",
16698
+ background: "var(--dsw-alias-bg-base)",
16699
+ border: "1px solid var(--dsw-alias-border-l2)",
16700
+ boxShadow: "0 1px 3px rgba(0,0,0,0.04)",
16701
+ boxSizing: "border-box",
16702
+ fontSize: "12px",
16703
+ lineHeight: "1.4",
16704
+ cursor: "pointer",
16705
+ pointerEvents: "auto"
16706
+ };
16707
+ var StartNode = (0, import_react11.forwardRef)(
16708
+ function StartNode2(props, ref) {
16709
+ const { section, x } = props;
16710
+ return import_react11.default.createElement(
16711
+ "div",
16712
+ {
16713
+ ref,
16714
+ "data-node-id": `stage:${section.stageId}`,
16715
+ "data-node-kind": "stage",
16716
+ style: {
16717
+ position: "absolute",
16718
+ left: `${x - 70}px`,
16719
+ top: `${section.y}px`,
16720
+ width: "140px",
16721
+ minHeight: "48px",
16722
+ padding: "10px 16px",
16723
+ borderRadius: "7px",
16724
+ background: "var(--dsw-alias-bg-base)",
16725
+ border: "1.5px solid var(--dsw-status-done, #22c55e)",
16726
+ boxSizing: "border-box",
16727
+ display: "flex",
16728
+ alignItems: "center",
16729
+ justifyContent: "center",
16730
+ gap: "8px",
16731
+ fontSize: "13px",
16732
+ fontWeight: 600,
16733
+ color: "var(--dsw-alias-label-primary)",
16734
+ lineHeight: "20px",
16735
+ pointerEvents: "auto"
16736
+ }
16737
+ },
16738
+ import_react11.default.createElement("div", {
16739
+ style: {
16740
+ width: "6px",
16741
+ height: "6px",
16742
+ borderRadius: "50%",
16743
+ background: "var(--dsw-status-done, #22c55e)",
16744
+ flexShrink: 0
16745
+ }
16746
+ }),
16747
+ section.label
16748
+ );
16749
+ }
16750
+ );
16751
+ var StageNode = (0, import_react11.forwardRef)(
16752
+ function StageNode2(props, ref) {
16753
+ const { section, x, onClick } = props;
16754
+ const status = section.status;
16755
+ const borderColor = status === "completed" ? "var(--dsw-status-done, #22c55e)" : status === "active" ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-border-l2)";
16756
+ const dotState = status === "completed" ? "done" : status === "active" ? "ongoing" : "ongoing";
16757
+ return import_react11.default.createElement(
16758
+ "div",
16759
+ {
16760
+ ref,
16761
+ "data-node-id": `stage:${section.stageId}`,
16762
+ "data-node-kind": "stage",
16763
+ role: "button",
16764
+ tabIndex: 0,
16765
+ onClick,
16766
+ onKeyDown: (e) => {
16767
+ if (e.key === "Enter" || e.key === " ") {
16768
+ e.preventDefault();
16769
+ onClick?.();
16770
+ }
16771
+ },
16772
+ style: {
16773
+ ...CARD,
16774
+ left: `${x - 115}px`,
16775
+ top: `${section.y}px`,
16776
+ width: "230px",
16777
+ minHeight: "72px",
16778
+ padding: "14px 16px",
16779
+ border: `1.5px solid ${borderColor}`,
16780
+ display: "flex",
16781
+ alignItems: "center",
16782
+ gap: "10px"
16783
+ }
16784
+ },
16785
+ import_react11.default.createElement(import_dsh_client_ui_primitives4.StateDot, { state: dotState, size: 8 }),
16786
+ import_react11.default.createElement(
16787
+ "div",
16788
+ { style: { minWidth: 0 } },
16789
+ import_react11.default.createElement("div", {
16790
+ style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)", fontSize: "14px", lineHeight: "20px" }
16791
+ }, section.label),
16792
+ import_react11.default.createElement("div", {
16793
+ style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary)", marginTop: "2px" }
16794
+ }, section.sublabel)
16795
+ )
16796
+ );
16797
+ }
16798
+ );
16799
+ var AgentNode = (0, import_react11.forwardRef)(
16800
+ function AgentNode2(props, ref) {
16801
+ const { branch, x, liveActivity, onClick } = props;
16802
+ const dotState = branch.status === "failed" ? "error" : branch.status === "closed" ? "done" : liveActivity === "running" ? "ongoing" : liveActivity === "inactive" ? "done" : branch.status === "starting" ? "ongoing" : "done";
16803
+ 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(
16805
+ "div",
16806
+ {
16807
+ ref,
16808
+ "data-node-id": `delegation:${branch.delegationId}`,
16809
+ "data-node-kind": "agent",
16810
+ role: "button",
16811
+ tabIndex: 0,
16812
+ onClick,
16813
+ onKeyDown: (e) => {
16814
+ if (e.key === "Enter" || e.key === " ") {
16815
+ e.preventDefault();
16816
+ onClick?.();
16817
+ }
16818
+ },
16819
+ style: {
16820
+ ...CARD,
16821
+ left: `${x - 97}px`,
16822
+ top: `${branch.y}px`,
16823
+ width: "195px",
16824
+ minHeight: "68px",
16825
+ padding: "12px 14px",
16826
+ border: `1px solid ${borderColor}`,
16827
+ display: "flex",
16828
+ alignItems: "center",
16829
+ gap: "8px"
16830
+ }
16831
+ },
16832
+ import_react11.default.createElement(import_dsh_client_ui_primitives4.StateDot, { state: dotState, size: 8 }),
16833
+ import_react11.default.createElement(
16834
+ "div",
16835
+ { style: { minWidth: 0 } },
16836
+ import_react11.default.createElement("div", {
16837
+ style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)", fontSize: "13px", lineHeight: "18px" }
16838
+ }, branch.label),
16839
+ import_react11.default.createElement("div", {
16840
+ style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", marginTop: "2px" }
16841
+ }, branch.status === "starting" ? "Agent starting" : branch.status === "active" ? "Agent active" : "Agent completed")
16842
+ )
16843
+ );
16844
+ }
16845
+ );
16846
+ var DELIVERY_ICON_COLORS = {
16847
+ result: "var(--dsw-status-done, #22c55e)",
16848
+ evidence: "#8b5cf6",
16849
+ artifact: "#f59e0b",
16850
+ raw_output: "var(--dsw-alias-label-tertiary)",
16851
+ finding: "var(--dsw-status-error, #ef4444)",
16852
+ asset: "#3b82f6"
16853
+ };
16854
+ var DeliveryNode = (0, import_react11.forwardRef)(
16855
+ function DeliveryNode2(props, ref) {
16856
+ const { group, x, y, onClick } = props;
16857
+ const iconColor = DELIVERY_ICON_COLORS[group.kind] ?? "var(--dsw-alias-label-tertiary)";
16858
+ return import_react11.default.createElement(
16859
+ "div",
16860
+ {
16861
+ ref,
16862
+ "data-node-id": `delivery:${group.kind}`,
16863
+ "data-node-kind": "delivery",
16864
+ role: "button",
16865
+ tabIndex: 0,
16866
+ onClick,
16867
+ onKeyDown: (e) => {
16868
+ if (e.key === "Enter" || e.key === " ") {
16869
+ e.preventDefault();
16870
+ onClick?.();
16871
+ }
16872
+ },
16873
+ title: group.sampleNames.slice(0, 5).join("\n") + (group.count > 5 ? `
16874
+ +${group.count - 5} more` : ""),
16875
+ style: {
16876
+ ...CARD,
16877
+ left: `${x - 55}px`,
16878
+ top: `${y}px`,
16879
+ width: "110px",
16880
+ minHeight: "62px",
16881
+ padding: "8px 10px",
16882
+ display: "flex",
16883
+ flexDirection: "column",
16884
+ alignItems: "center",
16885
+ justifyContent: "center",
16886
+ gap: "4px",
16887
+ textAlign: "center"
16888
+ }
16889
+ },
16890
+ import_react11.default.createElement("div", {
16891
+ style: {
16892
+ width: "8px",
16893
+ height: "8px",
16894
+ borderRadius: "4px",
16895
+ background: iconColor,
16896
+ flexShrink: 0
16897
+ }
16898
+ }),
16899
+ import_react11.default.createElement("div", {
16900
+ style: { fontWeight: 600, color: "var(--dsw-alias-label-primary)", fontSize: "12px", lineHeight: "16px" }
16901
+ }, group.label),
16902
+ import_react11.default.createElement("div", {
16903
+ style: { fontSize: "10px", color: "var(--dsw-alias-label-tertiary)" }
16904
+ }, `${group.count} ${group.count === 1 ? "file" : "files"}`)
16905
+ );
16906
+ }
16907
+ );
16908
+
16909
+ // src/ui/client/pentester/trace/TraceConnectors.tsx
16910
+ var import_react12 = __toESM(require("react"), 1);
16911
+ function roundedOrthogonal(sx, sy, tx, ty, r = 6) {
16912
+ if (Math.abs(sx - tx) < 2) {
16913
+ return `M ${sx} ${sy} L ${tx} ${ty}`;
16914
+ }
16915
+ const midY = (sy + ty) / 2;
16916
+ const dir = tx > sx ? 1 : -1;
16917
+ return [
16918
+ `M ${sx} ${sy}`,
16919
+ `L ${sx} ${midY - r}`,
16920
+ `A ${r} ${r} 0 0 ${dir > 0 ? 1 : 0} ${sx + dir * r} ${midY}`,
16921
+ `L ${tx - dir * r} ${midY}`,
16922
+ `A ${r} ${r} 0 0 ${dir > 0 ? 1 : 0} ${tx} ${midY + r}`,
16923
+ `L ${tx} ${ty}`
16924
+ ].join(" ");
16925
+ }
16926
+ function branchJunction(cx, sy, targets) {
16927
+ if (targets.length === 0) return [];
16928
+ const trunkY = sy + 18;
16929
+ return targets.map(
16930
+ (t) => `M ${cx} ${sy} L ${cx} ${trunkY} L ${t.tx} ${trunkY} L ${t.tx} ${t.ty}`
16931
+ );
16932
+ }
16933
+ function TraceConnectors(props) {
16934
+ const { layout } = props;
16935
+ const paths = (0, import_react12.useMemo)(() => {
16936
+ const result = [];
16937
+ const cx = layout.spineX;
16938
+ const solid = { stroke: "var(--dsw-alias-border-l2)", strokeWidth: 1.25 };
16939
+ const dashed = { stroke: "var(--dsw-alias-border-l1)", strokeWidth: 1, strokeDasharray: "4 3" };
16940
+ for (let i = 0; i < layout.sections.length - 1; i++) {
16941
+ const s = layout.sections[i];
16942
+ const next = layout.sections[i + 1];
16943
+ const y1 = s.y + STAGE_NODE_H;
16944
+ const y2 = next.y;
16945
+ if (y2 > y1) {
16946
+ result.push({
16947
+ d: `M ${cx} ${y1} L ${cx} ${y2}`,
16948
+ ...solid
16949
+ });
16950
+ }
16951
+ }
16952
+ for (const s of layout.sections) {
16953
+ const sy = s.y + STAGE_NODE_H;
16954
+ const targets = s.agentBranches.map((a) => ({
16955
+ tx: cx + a.x,
16956
+ ty: a.y
16957
+ }));
16958
+ if (targets.length === 1) {
16959
+ result.push({
16960
+ d: roundedOrthogonal(cx, sy, targets[0].tx, targets[0].ty),
16961
+ ...dashed
16962
+ });
16963
+ } else if (targets.length > 1) {
16964
+ const branches = branchJunction(cx, sy, targets);
16965
+ for (const b of branches) {
16966
+ result.push({ d: b, ...dashed });
16967
+ }
16968
+ }
16969
+ if (targets.length > 0) {
16970
+ const trunkY = sy + 18;
16971
+ result.push({
16972
+ d: `M ${cx - 3} ${trunkY} A 3 3 0 1 1 ${cx + 3} ${trunkY} A 3 3 0 1 1 ${cx - 3} ${trunkY}`,
16973
+ stroke: "var(--dsw-alias-border-l2)",
16974
+ strokeWidth: 1
16975
+ });
16976
+ }
16977
+ for (const a of s.agentBranches) {
16978
+ if (a.deliveryGroups.length === 0) continue;
16979
+ const ax = cx + a.x;
16980
+ const ay = a.y + AGENT_NODE_H;
16981
+ const firstDY = a.deliveryY;
16982
+ result.push({
16983
+ d: `M ${ax} ${ay} L ${ax} ${firstDY}`,
16984
+ ...dashed
16985
+ });
16986
+ for (let ri = 0; ri < a.deliveryRows.length; ri++) {
16987
+ const row3 = a.deliveryRows[ri];
16988
+ const rowY = a.deliveryY + ri * (DELIVERY_NODE_H + DELIVERY_ROW_GAP);
16989
+ const trunkY = rowY - 10;
16990
+ if (row3.length > 1) {
16991
+ const minDX = Math.min(...row3.map((dx) => cx + dx));
16992
+ const maxDX = Math.max(...row3.map((dx) => cx + dx));
16993
+ result.push({
16994
+ d: `M ${minDX} ${trunkY} L ${maxDX} ${trunkY}`,
16995
+ ...dashed
16996
+ });
16997
+ for (const dx of row3) {
16998
+ result.push({
16999
+ d: `M ${cx + dx} ${trunkY} L ${cx + dx} ${rowY}`,
17000
+ ...dashed
17001
+ });
17002
+ }
17003
+ }
17004
+ if (ri < a.deliveryRows.length - 1) {
17005
+ const nextRowY = a.deliveryY + (ri + 1) * (DELIVERY_NODE_H + DELIVERY_ROW_GAP);
17006
+ result.push({
17007
+ d: `M ${ax} ${rowY + DELIVERY_NODE_H} L ${ax} ${nextRowY}`,
17008
+ ...dashed
17009
+ });
17010
+ }
17011
+ }
17012
+ }
17013
+ }
17014
+ return result;
17015
+ }, [layout]);
17016
+ return import_react12.default.createElement(
17017
+ "svg",
17018
+ {
17019
+ width: layout.worldHalfWidth * 2,
17020
+ height: layout.worldHeight,
17021
+ style: {
17022
+ position: "absolute",
17023
+ inset: 0,
17024
+ overflow: "visible",
17025
+ pointerEvents: "none"
17026
+ }
17027
+ },
17028
+ ...paths.map(
17029
+ (p, i) => import_react12.default.createElement("path", {
17030
+ key: i,
17031
+ d: p.d,
17032
+ fill: "none",
17033
+ stroke: p.stroke,
17034
+ strokeWidth: p.strokeWidth,
17035
+ strokeDasharray: p.strokeDasharray,
17036
+ strokeLinecap: "round",
17037
+ strokeLinejoin: "round"
17038
+ })
17039
+ )
17040
+ );
17041
+ }
17042
+
17043
+ // src/ui/client/pentester/trace/TraceControls.tsx
17044
+ var import_react13 = __toESM(require("react"), 1);
17045
+ var BTN = {
17046
+ height: "32px",
17047
+ display: "flex",
17048
+ alignItems: "center",
17049
+ justifyContent: "center",
17050
+ border: "1px solid var(--dsw-alias-border-l2)",
17051
+ borderRadius: "6px",
17052
+ background: "var(--dsw-alias-bg-base)",
17053
+ color: "var(--dsw-alias-label-secondary)",
17054
+ cursor: "pointer",
17055
+ fontSize: "13px",
17056
+ fontFamily: "inherit",
17057
+ padding: "0 10px",
17058
+ lineHeight: "1"
17059
+ };
17060
+ function TraceControls(props) {
17061
+ const { scale, onZoomIn, onZoomOut, onReset, onFit } = props;
17062
+ const pct = Math.round(scale * 100);
17063
+ return import_react13.default.createElement(
17064
+ "div",
17065
+ {
17066
+ style: { display: "flex", alignItems: "center", gap: "4px" }
17067
+ },
17068
+ import_react13.default.createElement("button", {
17069
+ type: "button",
17070
+ "aria-label": "Zoom out",
17071
+ title: "Zoom out",
17072
+ onClick: onZoomOut,
17073
+ style: { ...BTN, width: "32px", padding: 0 }
17074
+ }, "\u2212"),
17075
+ import_react13.default.createElement("button", {
17076
+ type: "button",
17077
+ "aria-label": "Reset zoom",
17078
+ title: "Reset to 100%",
17079
+ onClick: onReset,
17080
+ style: { ...BTN, fontWeight: 500, fontSize: "11px", color: "var(--dsw-alias-label-primary)" }
17081
+ }, `${pct}%`),
17082
+ import_react13.default.createElement("button", {
17083
+ type: "button",
17084
+ "aria-label": "Zoom in",
17085
+ title: "Zoom in",
17086
+ onClick: onZoomIn,
17087
+ style: { ...BTN, width: "32px", padding: 0 }
17088
+ }, "+"),
17089
+ import_react13.default.createElement("button", {
17090
+ type: "button",
17091
+ "aria-label": "Fit to width",
17092
+ title: "Fit topology width",
17093
+ onClick: onFit,
17094
+ style: { ...BTN, fontWeight: 500, fontSize: "11px" }
17095
+ }, "Fit")
17096
+ );
17097
+ }
17098
+
17099
+ // src/ui/client/pentester/trace/TraceTimeline.tsx
17100
+ function getScrollOwner() {
17101
+ return document.scrollingElement ?? document.documentElement;
17102
+ }
17103
+ function checkSpineInvariant(graphEl, scale) {
17104
+ if (graphEl === null) return;
17105
+ const startEl = graphEl.querySelector('[data-node-id="stage:start"]');
17106
+ if (startEl === null) return;
17107
+ const graphRect = graphEl.getBoundingClientRect();
17108
+ const startRect = startEl.getBoundingClientRect();
17109
+ const startCenter = startRect.left + startRect.width / 2;
17110
+ const graphCenter = graphRect.left + graphRect.width / 2;
17111
+ const error51 = Math.abs(startCenter - graphCenter);
17112
+ if (error51 > 2) {
17113
+ console.warn(`[Trace] Spine invariant violated: error=${error51.toFixed(1)}px at scale=${scale.toFixed(2)}`);
17114
+ }
17115
+ }
17116
+ function TraceTimeline(props) {
17117
+ 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)(
17124
+ () => buildScaledTraceLayout(baseLayout, scale),
17125
+ [baseLayout, scale]
17126
+ );
17127
+ const adjustedSections = scaledLayout.sections;
17128
+ const scaledBands = scaledLayout.bands;
17129
+ const worldHalfWidth = scaledLayout.worldHalfWidth;
17130
+ const spineX = scaledLayout.spineX;
17131
+ const worldW = worldHalfWidth * 2;
17132
+ const worldH = scaledLayout.totalHeight;
17133
+ const doAutoFit = (0, import_react14.useCallback)(() => {
17134
+ const vp = graphVpRef.current;
17135
+ if (vp === null) return DEFAULT_SCALE;
17136
+ const availableHalf = vp.clientWidth / 2 - GRAPH_PADDING;
17137
+ const requiredHalf = baseLayout.worldHalfWidth;
17138
+ const fitScale = availableHalf / requiredHalf;
17139
+ return Math.min(DEFAULT_SCALE, Math.max(MIN_SCALE, fitScale));
17140
+ }, [baseLayout.worldHalfWidth]);
17141
+ (0, import_react14.useEffect)(() => {
17142
+ if (autoFitDoneRef.current) return;
17143
+ const fitScale = doAutoFit();
17144
+ if (fitScale !== DEFAULT_SCALE) setScale(fitScale);
17145
+ autoFitDoneRef.current = true;
17146
+ }, [doAutoFit]);
17147
+ const zoomTo = (0, import_react14.useCallback)((newScale) => {
17148
+ const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale));
17149
+ const owner = getScrollOwner();
17150
+ const oldScale = scale;
17151
+ const viewH = owner.clientHeight;
17152
+ const scrollTop = owner.scrollTop;
17153
+ const worldCenterY = (scrollTop + viewH / 2) / oldScale;
17154
+ const nextScrollTop = worldCenterY * clamped - viewH / 2;
17155
+ setScale(clamped);
17156
+ requestAnimationFrame(() => {
17157
+ owner.scrollTop = Math.max(0, nextScrollTop);
17158
+ });
17159
+ }, [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) => {
17165
+ if (!e.ctrlKey && !e.metaKey) return;
17166
+ e.preventDefault();
17167
+ const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12;
17168
+ zoomTo(scale * factor);
17169
+ }, [zoomTo, scale]);
17170
+ (0, import_react14.useEffect)(() => {
17171
+ checkSpineInvariant(graphRef.current, scale);
17172
+ }, [scale, adjustedSections]);
17173
+ const handleStageClick = (0, import_react14.useCallback)((stageId) => {
17174
+ onNavigate("stages", { stage: stageId });
17175
+ }, [onNavigate]);
17176
+ const handleAgentClick = (0, import_react14.useCallback)((sessionId) => {
17177
+ if (sessionId !== void 0) onOpenSession(sessionId);
17178
+ }, [onOpenSession]);
17179
+ const handleDeliveryClick = (0, import_react14.useCallback)((group) => {
17180
+ if (group.navTab === "findings") {
17181
+ onNavigate("findings", { finding: group.navPath });
17182
+ } else {
17183
+ onNavigate("output", { output: group.navPath });
17184
+ }
17185
+ }, [onNavigate]);
17186
+ const gridCols = `${TIME_PANEL_W}px minmax(0, 1fr)`;
17187
+ const timelineEventGrid = `${TIMELINE_DOT_W}px ${TIMELINE_TIME_W}px minmax(0, 1fr)`;
17188
+ return import_react14.default.createElement(
17189
+ "div",
17190
+ {
17191
+ style: {
17192
+ display: "flex",
17193
+ flexDirection: "column",
17194
+ width: "100%",
17195
+ border: "1px solid var(--dsw-alias-border-l2)",
17196
+ borderRadius: "8px",
17197
+ background: "var(--dsw-alias-bg-base)"
17198
+ }
17199
+ },
17200
+ // ── TraceHeader ─────────────────────────────────────────────────────────
17201
+ import_react14.default.createElement(
17202
+ "div",
17203
+ {
17204
+ "data-testid": "trace-header",
17205
+ style: {
17206
+ display: "grid",
17207
+ gridTemplateColumns: gridCols,
17208
+ flexShrink: 0,
17209
+ borderBottom: "1px solid var(--dsw-alias-border-l1)",
17210
+ background: "var(--dsw-alias-bg-base)"
17211
+ }
17212
+ },
17213
+ import_react14.default.createElement(
17214
+ "div",
17215
+ {
17216
+ style: { padding: "12px 18px", borderRight: "1px solid var(--dsw-alias-border-l1)" }
17217
+ },
17218
+ import_react14.default.createElement("div", {
17219
+ style: { fontSize: "13px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
17220
+ }, "Timeline")
17221
+ ),
17222
+ import_react14.default.createElement(
17223
+ "div",
17224
+ {
17225
+ style: {
17226
+ padding: "12px 18px",
17227
+ display: "flex",
17228
+ alignItems: "center",
17229
+ justifyContent: "space-between"
17230
+ }
17231
+ },
17232
+ import_react14.default.createElement("div", {
17233
+ style: { fontSize: "13px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" }
17234
+ }, "Topology"),
17235
+ import_react14.default.createElement(TraceControls, {
17236
+ scale,
17237
+ onZoomIn: zoomIn,
17238
+ onZoomOut: zoomOut,
17239
+ onReset: resetZoom,
17240
+ onFit: fitWidth
17241
+ })
17242
+ )
17243
+ ),
17244
+ // ── Content (no scroll — flows into Harness scroll owner) ───────────────
17245
+ import_react14.default.createElement(
17246
+ "div",
17247
+ {
17248
+ onWheel: handleWheel,
17249
+ style: {
17250
+ display: "grid",
17251
+ gridTemplateColumns: gridCols
17252
+ }
17253
+ },
17254
+ // ── Timeline rail ────────────────────────────────────────────────────
17255
+ import_react14.default.createElement(
17256
+ "div",
17257
+ {
17258
+ style: {
17259
+ position: "relative",
17260
+ borderRight: "1px solid var(--dsw-alias-border-l1)",
17261
+ background: "var(--dsw-alias-bg-base)",
17262
+ height: `${worldH}px`
17263
+ }
17264
+ },
17265
+ // Vertical timeline spine line
17266
+ import_react14.default.createElement("div", {
17267
+ "aria-hidden": true,
17268
+ style: {
17269
+ position: "absolute",
17270
+ left: "22px",
17271
+ top: "8px",
17272
+ bottom: "8px",
17273
+ width: "1px",
17274
+ background: "var(--dsw-alias-border-l2)",
17275
+ zIndex: 0
17276
+ }
17277
+ }),
17278
+ ...scaledBands.map((band) => {
17279
+ const isStart = band.bandId === "band:start";
17280
+ if (band.timelineItems.length === 0) return null;
17281
+ return import_react14.default.createElement(
17282
+ "div",
17283
+ {
17284
+ key: band.bandId,
17285
+ style: {
17286
+ position: "absolute",
17287
+ top: `${band.visualY}px`,
17288
+ left: "0px",
17289
+ right: "4px",
17290
+ display: "flex",
17291
+ flexDirection: "column",
17292
+ gap: "4px",
17293
+ zIndex: 1
17294
+ }
17295
+ },
17296
+ // Start band
17297
+ isStart && band.timelineItems.map(
17298
+ (item, ii) => import_react14.default.createElement(
17299
+ "div",
17300
+ {
17301
+ key: ii,
17302
+ style: {
17303
+ display: "grid",
17304
+ gridTemplateColumns: timelineEventGrid,
17305
+ alignItems: "flex-start",
17306
+ gap: "6px",
17307
+ minHeight: `${TIMELINE_ROW_H}px`
17308
+ }
17309
+ },
17310
+ import_react14.default.createElement(
17311
+ "div",
17312
+ {
17313
+ style: { display: "flex", justifyContent: "center", paddingTop: "4px" }
17314
+ },
17315
+ import_react14.default.createElement("div", {
17316
+ style: {
17317
+ width: "6px",
17318
+ height: "6px",
17319
+ borderRadius: "50%",
17320
+ background: item.dotColor ?? "var(--dsw-status-done, #22c55e)"
17321
+ }
17322
+ })
17323
+ ),
17324
+ import_react14.default.createElement("span", {
17325
+ style: {
17326
+ fontSize: "11px",
17327
+ fontFamily: "monospace",
17328
+ color: "var(--dsw-alias-label-caption, var(--dsw-alias-label-tertiary))",
17329
+ lineHeight: "18px",
17330
+ paddingTop: "2px"
17331
+ }
17332
+ }, item.timeLabel),
17333
+ import_react14.default.createElement(
17334
+ "div",
17335
+ { style: { minWidth: 0, paddingTop: "2px" } },
17336
+ import_react14.default.createElement("div", {
17337
+ style: { fontSize: "13px", fontWeight: 500, color: "var(--dsw-alias-label-primary)", lineHeight: "18px" }
17338
+ }, item.title),
17339
+ item.subtitle !== "" && import_react14.default.createElement("div", {
17340
+ style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", lineHeight: "16px" }
17341
+ }, item.subtitle)
17342
+ )
17343
+ )
17344
+ ),
17345
+ // Regular stage/agent items
17346
+ !isStart && band.timelineItems.map(
17347
+ (item, ii) => import_react14.default.createElement(
17348
+ "div",
17349
+ {
17350
+ key: ii,
17351
+ style: {
17352
+ display: "grid",
17353
+ gridTemplateColumns: timelineEventGrid,
17354
+ alignItems: "flex-start",
17355
+ gap: "6px",
17356
+ minHeight: `${TIMELINE_ROW_H}px`
17357
+ }
17358
+ },
17359
+ import_react14.default.createElement(
17360
+ "div",
17361
+ {
17362
+ style: { display: "flex", justifyContent: "center", paddingTop: "4px" }
17363
+ },
17364
+ import_react14.default.createElement("div", {
17365
+ style: {
17366
+ width: "8px",
17367
+ height: "8px",
17368
+ borderRadius: "50%",
17369
+ background: item.dotColor ?? "var(--dsw-alias-label-tertiary)"
17370
+ }
17371
+ })
17372
+ ),
17373
+ import_react14.default.createElement("span", {
17374
+ style: {
17375
+ fontSize: "11px",
17376
+ fontFamily: "monospace",
17377
+ color: "var(--dsw-alias-label-caption, var(--dsw-alias-label-tertiary))",
17378
+ lineHeight: "18px",
17379
+ paddingTop: "2px"
17380
+ }
17381
+ }, item.timeLabel),
17382
+ import_react14.default.createElement(
17383
+ "div",
17384
+ { style: { minWidth: 0, paddingTop: "2px" } },
17385
+ import_react14.default.createElement("div", {
17386
+ style: { fontSize: "13px", fontWeight: 500, color: "var(--dsw-alias-label-primary)", lineHeight: "18px" }
17387
+ }, item.title),
17388
+ item.subtitle !== "" && import_react14.default.createElement("div", {
17389
+ style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", lineHeight: "16px" }
17390
+ }, item.subtitle)
17391
+ )
17392
+ )
17393
+ )
17394
+ );
17395
+ })
17396
+ ),
17397
+ // ── Graph viewport ────────────────────────────────────────────────────
17398
+ import_react14.default.createElement(
17399
+ "div",
17400
+ {
17401
+ ref: graphVpRef,
17402
+ style: {
17403
+ position: "relative",
17404
+ background: "var(--dsw-alias-bg-base)",
17405
+ overflow: "hidden",
17406
+ height: `${worldH}px`
17407
+ }
17408
+ },
17409
+ // SpineAnchor: CSS left:50%, always at viewport center
17410
+ import_react14.default.createElement(
17411
+ "div",
17412
+ {
17413
+ style: {
17414
+ position: "absolute",
17415
+ left: "50%",
17416
+ top: "0px",
17417
+ width: "0px",
17418
+ height: `${worldH}px`,
17419
+ pointerEvents: "none"
17420
+ }
17421
+ },
17422
+ // TopologyWorld: symmetric around spine
17423
+ import_react14.default.createElement(
17424
+ "div",
17425
+ {
17426
+ ref: graphRef,
17427
+ style: {
17428
+ position: "absolute",
17429
+ left: `-${worldHalfWidth}px`,
17430
+ top: "0px",
17431
+ width: `${worldW}px`,
17432
+ height: `${worldH}px`,
17433
+ transform: `scale(${scale})`,
17434
+ transformOrigin: `${spineX}px 0px`
17435
+ }
17436
+ },
17437
+ import_react14.default.createElement(TraceConnectors, {
17438
+ layout: { ...baseLayout, sections: adjustedSections, worldHeight: worldH, spineX },
17439
+ rects: /* @__PURE__ */ new Map()
17440
+ }),
17441
+ import_react14.default.createElement(
17442
+ "div",
17443
+ {
17444
+ style: { position: "absolute", inset: 0, zIndex: 1 }
17445
+ },
17446
+ ...adjustedSections.filter((s) => s.stageId === "start").map(
17447
+ (section) => import_react14.default.createElement(StartNode, {
17448
+ key: "stage:start",
17449
+ section,
17450
+ x: spineX
17451
+ })
17452
+ ),
17453
+ ...adjustedSections.filter((s) => s.stageId !== "start").map(
17454
+ (section) => import_react14.default.createElement(StageNode, {
17455
+ key: `stage:${section.stageId}`,
17456
+ section,
17457
+ x: spineX,
17458
+ onClick: () => handleStageClick(section.stageId)
17459
+ })
17460
+ ),
17461
+ ...adjustedSections.flatMap(
17462
+ (section) => section.agentBranches.map(
17463
+ (branch) => import_react14.default.createElement(AgentNode, {
17464
+ key: `agent:${branch.delegationId}`,
17465
+ branch,
17466
+ x: spineX + branch.x,
17467
+ liveActivity: liveActivity.get(branch.delegationId),
17468
+ onClick: () => handleAgentClick(branch.sessionId)
17469
+ })
17470
+ )
17471
+ ),
17472
+ ...adjustedSections.flatMap(
17473
+ (section) => section.agentBranches.flatMap(
17474
+ (branch) => branch.deliveryRows.flatMap(
17475
+ (row3, ri) => row3.map((dx, gi) => {
17476
+ const group = branch.deliveryGroups[ri * 3 + gi];
17477
+ if (group === void 0) return null;
17478
+ return import_react14.default.createElement(DeliveryNode, {
17479
+ key: `delivery:${branch.delegationId}:${group.kind}`,
17480
+ group,
17481
+ x: spineX + dx,
17482
+ y: branch.deliveryY + ri * (DELIVERY_NODE_H + DELIVERY_ROW_GAP),
17483
+ onClick: () => handleDeliveryClick(group)
17484
+ });
17485
+ })
17486
+ ).filter(Boolean)
17487
+ )
17488
+ )
17489
+ )
17490
+ )
17491
+ )
17492
+ )
17493
+ )
17494
+ );
17495
+ }
17496
+
17497
+ // src/ui/client/pentester/TraceView.tsx
17498
+ function TraceView(props) {
17499
+ const { snapshot, liveActivity, onNavigate, onOpenSession, activeTargetId } = props;
17500
+ if (snapshot === null || snapshot.run === null) {
17501
+ return import_react15.default.createElement("div", {
17502
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17503
+ }, "No execution trace yet");
17504
+ }
17505
+ return import_react15.default.createElement(TraceTimeline, {
17506
+ snapshot,
17507
+ liveActivity,
17508
+ onNavigate,
17509
+ onOpenSession,
17510
+ activeTargetId
17511
+ });
17512
+ }
17513
+
17514
+ // 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");
17517
+
17518
+ // 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");
17521
+ function FolderTreeRow(props) {
17522
+ 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;
17525
+ const folderColor = containsSelected ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-label-secondary)";
17526
+ return import_react16.default.createElement(
17527
+ "div",
17528
+ null,
17529
+ import_react16.default.createElement(
17530
+ "div",
17531
+ {
17532
+ role: "button",
17533
+ tabIndex: 0,
17534
+ "aria-expanded": open,
17535
+ onClick: () => setOpen(!open),
17536
+ onKeyDown: (e) => {
17537
+ if (e.key === "Enter" || e.key === " ") {
17538
+ e.preventDefault();
17539
+ setOpen(!open);
17540
+ }
17541
+ },
17542
+ style: {
17543
+ display: "flex",
17544
+ alignItems: "center",
17545
+ gap: "4px",
17546
+ marginLeft: `${depth * 16}px`,
17547
+ padding: "3px 6px",
17548
+ borderRadius: "4px",
17549
+ cursor: "pointer",
17550
+ fontSize: "13px",
17551
+ lineHeight: "20px",
17552
+ color: "var(--dsw-alias-label-primary)",
17553
+ userSelect: "none",
17554
+ height: "28px",
17555
+ boxSizing: "border-box"
17556
+ },
17557
+ onMouseEnter: (e) => {
17558
+ e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover)";
17559
+ },
17560
+ onMouseLeave: (e) => {
17561
+ e.currentTarget.style.background = "transparent";
17562
+ }
17563
+ },
17564
+ // Chevron
17565
+ hasChildren ? import_react16.default.createElement(
17566
+ "span",
17567
+ { 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 } }),
17570
+ // Folder icon — color reflects selected ancestry
17571
+ import_react16.default.createElement(
17572
+ "span",
17573
+ { 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 })
17575
+ ),
17576
+ // Name
17577
+ import_react16.default.createElement("span", {
17578
+ style: {
17579
+ overflow: "hidden",
17580
+ textOverflow: "ellipsis",
17581
+ whiteSpace: "nowrap"
17582
+ }
17583
+ }, name)
17584
+ ),
17585
+ open && hasChildren && import_react16.default.createElement("div", null, children)
17586
+ );
17587
+ }
17588
+ function FileRow(props) {
17589
+ const { name, depth, size, isSelected = false, onClick } = props;
17590
+ return import_react16.default.createElement(
17591
+ "div",
17592
+ {
17593
+ role: "button",
17594
+ tabIndex: 0,
17595
+ onClick,
17596
+ onKeyDown: (e) => {
17597
+ if (e.key === "Enter" || e.key === " ") {
17598
+ e.preventDefault();
17599
+ onClick();
17600
+ }
17601
+ },
17602
+ style: {
17603
+ display: "flex",
17604
+ alignItems: "center",
17605
+ gap: "6px",
17606
+ marginLeft: `${depth * 16 + 16}px`,
17607
+ padding: "3px 8px",
17608
+ borderRadius: "4px",
17609
+ cursor: "pointer",
17610
+ background: isSelected ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
17611
+ fontSize: "13px",
17612
+ lineHeight: "20px",
17613
+ color: "var(--dsw-alias-label-primary)",
17614
+ height: "28px",
17615
+ boxSizing: "border-box"
17616
+ },
17617
+ onMouseEnter: (e) => {
17618
+ if (!isSelected) e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover)";
17619
+ },
17620
+ onMouseLeave: (e) => {
17621
+ if (!isSelected) e.currentTarget.style.background = "transparent";
17622
+ }
17623
+ },
17624
+ // File icon placeholder (Harness doesn't have a specific file icon; use chevron space)
17625
+ import_react16.default.createElement("span", {
17626
+ style: { width: "14px", flexShrink: 0 }
17627
+ }),
17628
+ import_react16.default.createElement("span", {
17629
+ style: {
17630
+ overflow: "hidden",
17631
+ textOverflow: "ellipsis",
17632
+ whiteSpace: "nowrap",
17633
+ flex: 1,
17634
+ minWidth: 0
17635
+ }
17636
+ }, name),
17637
+ size !== void 0 && import_react16.default.createElement("span", {
17638
+ style: {
17639
+ fontSize: "11px",
17640
+ color: "var(--dsw-alias-label-tertiary)",
17641
+ flexShrink: 0
17642
+ }
17643
+ }, size)
17644
+ );
17645
+ }
17646
+
17647
+ // src/ui/client/pentester/OutputView.tsx
17648
+ var OUTPUT_FILTER_TABS = [
17649
+ { id: "all", label: "All" },
17650
+ { id: "report", label: "Reports" },
17651
+ { id: "evidence", label: "Evidence" },
17652
+ { id: "artifact", label: "Artifacts" }
17653
+ ];
17654
+ function TargetIcon(props) {
17655
+ const s = props.size ?? 16;
17656
+ return import_react17.default.createElement(
17657
+ "svg",
17658
+ {
17659
+ width: s,
17660
+ height: s,
17661
+ viewBox: "0 0 16 16",
17662
+ fill: "none",
17663
+ stroke: "currentColor",
17664
+ strokeWidth: "1.5",
17665
+ strokeLinecap: "round",
17666
+ strokeLinejoin: "round",
17667
+ "aria-hidden": true,
17668
+ style: { flexShrink: 0 }
17669
+ },
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" })
17677
+ );
17678
+ }
17679
+ function entryContainsSelected(entry, selectedPath) {
17680
+ if (selectedPath === null) return false;
17681
+ if (entry.kind === "file") return entry.path === selectedPath;
17682
+ if (entry.children === void 0) return false;
17683
+ return entry.children.some((child) => entryContainsSelected(child, selectedPath));
17684
+ }
17685
+ function TargetRow(props) {
17686
+ const { targetId, target, current, defaultOpen, containsSelected, children } = props;
17687
+ const [open, setOpen] = (0, import_react17.useState)(defaultOpen);
17688
+ 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(
17690
+ "div",
17691
+ null,
17692
+ import_react17.default.createElement(
17693
+ "div",
17694
+ {
17695
+ role: "button",
17696
+ tabIndex: 0,
17697
+ "aria-expanded": open,
17698
+ "aria-label": `Target ${target}`,
17699
+ onClick: () => setOpen(!open),
17700
+ onKeyDown: (e) => {
17701
+ if (e.key === "Enter" || e.key === " ") {
17702
+ e.preventDefault();
17703
+ setOpen(!open);
17704
+ }
17705
+ },
17706
+ style: {
17707
+ display: "flex",
17708
+ alignItems: "center",
17709
+ gap: "6px",
17710
+ padding: "5px 8px",
17711
+ borderRadius: "4px",
17712
+ cursor: "pointer",
17713
+ fontSize: "13px",
17714
+ lineHeight: "20px",
17715
+ color: current ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)",
17716
+ fontWeight: current ? 500 : 400,
17717
+ userSelect: "none",
17718
+ height: "30px",
17719
+ boxSizing: "border-box"
17720
+ },
17721
+ onMouseEnter: (e) => {
17722
+ e.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover)";
17723
+ },
17724
+ onMouseLeave: (e) => {
17725
+ e.currentTarget.style.background = "transparent";
17726
+ }
17727
+ },
17728
+ import_react17.default.createElement("span", {
17729
+ style: {
17730
+ color: "var(--dsw-alias-label-tertiary)",
17731
+ flexShrink: 0,
17732
+ display: "flex",
17733
+ alignItems: "center",
17734
+ fontSize: "10px",
17735
+ width: "12px"
17736
+ }
17737
+ }, open ? "\u25BE" : "\u25B8"),
17738
+ import_react17.default.createElement("span", {
17739
+ style: {
17740
+ color: iconColor,
17741
+ flexShrink: 0,
17742
+ display: "flex",
17743
+ alignItems: "center"
17744
+ }
17745
+ }, import_react17.default.createElement(TargetIcon, { size: 16 })),
17746
+ import_react17.default.createElement("span", {
17747
+ style: {
17748
+ overflow: "hidden",
17749
+ textOverflow: "ellipsis",
17750
+ whiteSpace: "nowrap"
17751
+ }
17752
+ }, target),
17753
+ current && import_react17.default.createElement("span", {
17754
+ style: {
17755
+ fontSize: "10px",
17756
+ color: "var(--dsw-alias-state-business-primary)",
17757
+ background: "rgba(59, 130, 246, 0.1)",
17758
+ padding: "0 5px",
17759
+ borderRadius: "3px",
17760
+ lineHeight: "16px",
17761
+ flexShrink: 0,
17762
+ fontWeight: 500
17763
+ }
17764
+ }, "active")
17765
+ ),
17766
+ open && import_react17.default.createElement("div", { style: { marginLeft: "20px" } }, children)
17767
+ );
17768
+ }
17769
+ 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)(() => {
17779
+ if (snapshot === null) return;
17780
+ let cancelled = false;
17781
+ setOutputLoading(true);
17782
+ setOutputError(null);
17783
+ command({ kind: "listOutput", sessionId }).then((result) => {
17784
+ if (cancelled) return;
17785
+ setOutputLoading(false);
17786
+ if (result.ok && result.value !== null && typeof result.value === "object" && "children" in result.value) {
17787
+ setOutputTree(result.value);
17788
+ setOutputError(null);
17789
+ } else {
17790
+ setOutputError(result.ok === false ? result.error ?? "Failed to load output tree" : "Invalid response");
17791
+ }
17792
+ }).catch((err) => {
17793
+ if (cancelled) return;
17794
+ setOutputLoading(false);
17795
+ setOutputError(err instanceof Error ? err.message : "Failed to load output tree");
17796
+ });
17797
+ return () => {
17798
+ cancelled = true;
17799
+ };
17800
+ }, [command, sessionId, snapshot?.run?.id]);
17801
+ const targetTrees = outputTree !== null ? [outputTree] : [];
17802
+ const selectedPath = preview?.entry.path ?? null;
17803
+ const selectedTargetId = preview?.targetId ?? null;
17804
+ const filteredTargets = (0, import_react17.useMemo)(() => {
17805
+ const base = filter === "all" ? targetTrees : (() => {
17806
+ const categoryMap = {
17807
+ all: [],
17808
+ report: ["report"],
17809
+ evidence: ["evidence"],
17810
+ artifact: ["artifact", "finding", "stage"]
17811
+ };
17812
+ const allowed = new Set(categoryMap[filter]);
17813
+ return targetTrees.map((t) => ({
17814
+ ...t,
17815
+ children: filterEntries(t.children, allowed)
17816
+ })).filter((t) => t.children.length > 0);
17817
+ })();
17818
+ return base.map((t) => ({
17819
+ ...t,
17820
+ containsSelected: selectedTargetId === t.targetId && t.children.some(
17821
+ (child) => entryContainsSelected(child, selectedPath)
17822
+ )
17823
+ }));
17824
+ }, [targetTrees, filter, selectedPath, selectedTargetId]);
17825
+ const openPreview = (0, import_react17.useCallback)(async (entry, targetId) => {
17826
+ if (entry.kind === "directory") return;
17827
+ setPreview({ entry, targetId });
17828
+ setPreviewContent(null);
17829
+ setPreviewLoading(true);
17830
+ try {
17831
+ const result = await command({
17832
+ kind: "readOutput",
17833
+ path: entry.path,
17834
+ sessionId,
17835
+ targetId: targetId !== "" ? targetId : void 0
17836
+ });
17837
+ if (result.ok) {
17838
+ setPreviewContent(result.value);
17839
+ } else {
17840
+ setPreviewContent({ path: entry.path, name: entry.name, size: 0, mtime: "", mime: "text", content: "Unable to load." });
17841
+ }
17842
+ } catch {
17843
+ setPreviewContent({ path: entry.path, name: entry.name, size: 0, mtime: "", mime: "text", content: "Error loading." });
17844
+ } finally {
17845
+ setPreviewLoading(false);
17846
+ }
17847
+ }, [command, sessionId]);
17848
+ const closePreview = (0, import_react17.useCallback)(() => {
17849
+ setPreview(null);
17850
+ setPreviewContent(null);
17851
+ }, []);
17852
+ const initialLoading = outputTree === null && outputLoading;
17853
+ if (initialLoading) {
17854
+ return import_react17.default.createElement("div", {
17855
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17856
+ }, "Loading output tree...");
17857
+ }
17858
+ if (outputError !== null) {
17859
+ return import_react17.default.createElement(
17860
+ "div",
17861
+ { style: { padding: "24px" } },
17862
+ import_react17.default.createElement("div", {
17863
+ style: { color: "var(--dsw-status-error)", marginBottom: "12px", fontSize: "14px" }
17864
+ }, "Unable to load output tree"),
17865
+ import_react17.default.createElement("div", {
17866
+ style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px", marginBottom: "12px" }
17867
+ }, outputError),
17868
+ import_react17.default.createElement("button", {
17869
+ onClick: () => {
17870
+ setOutputError(null);
17871
+ setOutputTree(null);
17872
+ setOutputLoading(true);
17873
+ command({ kind: "listOutput", sessionId }).then((result) => {
17874
+ setOutputLoading(false);
17875
+ if (result.ok && result.value !== null && typeof result.value === "object" && "children" in result.value) {
17876
+ setOutputTree(result.value);
17877
+ } else {
17878
+ setOutputError(result.ok === false ? result.error ?? "Failed to load output tree" : "Invalid response");
17879
+ }
17880
+ }).catch((err) => {
17881
+ setOutputLoading(false);
17882
+ setOutputError(err instanceof Error ? err.message : "Failed to load output tree");
17883
+ });
17884
+ },
17885
+ style: {
17886
+ padding: "6px 16px",
17887
+ border: "1px solid var(--dsw-alias-border-l2)",
17888
+ borderRadius: "6px",
17889
+ background: "var(--dsw-alias-interactive-bg-hover)",
17890
+ color: "var(--dsw-alias-label-primary)",
17891
+ cursor: "pointer",
17892
+ fontSize: "13px"
17893
+ }
17894
+ }, "Retry Output")
17895
+ );
17896
+ }
17897
+ if (snapshot === null) {
17898
+ return import_react17.default.createElement("div", {
17899
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17900
+ }, "No output data");
17901
+ }
17902
+ if (targetTrees.length === 0) {
17903
+ return import_react17.default.createElement("div", {
17904
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
17905
+ }, "No output files");
17906
+ }
17907
+ return import_react17.default.createElement(
17908
+ "div",
17909
+ {
17910
+ style: { display: "flex", gap: "16px", minHeight: "400px", overflow: "visible" }
17911
+ },
17912
+ // Left: file tree
17913
+ import_react17.default.createElement(
17914
+ "div",
17915
+ {
17916
+ style: {
17917
+ flex: "0 0 320px",
17918
+ overflow: "auto",
17919
+ borderRight: "1px solid var(--dsw-alias-border-l2)",
17920
+ paddingRight: "12px",
17921
+ display: "flex",
17922
+ flexDirection: "column"
17923
+ }
17924
+ },
17925
+ import_react17.default.createElement(
17926
+ "div",
17927
+ { style: { marginBottom: "12px" } },
17928
+ import_react17.default.createElement(PentesterTabBar, {
17929
+ items: OUTPUT_FILTER_TABS,
17930
+ active: filter,
17931
+ onSelect: (id) => setFilter(id),
17932
+ variant: "compact"
17933
+ })
17934
+ ),
17935
+ // ── Tree: "Targets" section label + target rows at root depth ─────
17936
+ import_react17.default.createElement(
17937
+ "div",
17938
+ { style: { flex: 1 } },
17939
+ import_react17.default.createElement("div", {
17940
+ style: {
17941
+ fontSize: "11px",
17942
+ fontWeight: 600,
17943
+ color: "var(--dsw-alias-label-tertiary)",
17944
+ textTransform: "uppercase",
17945
+ letterSpacing: "0.5px",
17946
+ padding: "4px 8px",
17947
+ marginBottom: "4px"
17948
+ }
17949
+ }, "Targets"),
17950
+ ...filteredTargets.map(
17951
+ (t) => import_react17.default.createElement(TargetRow, {
17952
+ key: t.targetId,
17953
+ targetId: t.targetId,
17954
+ target: t.target,
17955
+ current: t.current,
17956
+ defaultOpen: t.current,
17957
+ containsSelected: t.containsSelected === true,
17958
+ children: t.children.length > 0 ? t.children.map(
17959
+ (entry) => renderOutputTree(entry, 0, t.targetId, openPreview, preview, selectedPath, selectedTargetId)
17960
+ ) : void 0
17961
+ })
17962
+ )
17963
+ )
17964
+ ),
17965
+ // Right: preview frame
17966
+ import_react17.default.createElement(
17967
+ "div",
17968
+ {
17969
+ style: {
17970
+ flex: 1,
17971
+ minWidth: 0,
17972
+ display: "flex",
17973
+ flexDirection: "column",
17974
+ overflow: "hidden",
17975
+ border: "1px solid var(--dsw-alias-border-l2)",
17976
+ borderRadius: "8px",
17977
+ background: "var(--dsw-alias-bg-base)",
17978
+ minHeight: "320px",
17979
+ maxHeight: "clamp(320px, 65vh, 680px)"
17980
+ }
17981
+ },
17982
+ preview === null ? import_react17.default.createElement("div", {
17983
+ style: {
17984
+ flex: 1,
17985
+ display: "flex",
17986
+ alignItems: "center",
17987
+ justifyContent: "center",
17988
+ color: "var(--dsw-alias-label-tertiary)",
17989
+ fontSize: "13px"
17990
+ }
17991
+ }, "Select a file to preview") : import_react17.default.createElement(
17992
+ import_react17.default.Fragment,
17993
+ null,
17994
+ import_react17.default.createElement(
17995
+ "div",
17996
+ {
17997
+ style: {
17998
+ flex: "none",
17999
+ display: "flex",
18000
+ alignItems: "center",
18001
+ justifyContent: "space-between",
18002
+ padding: "8px 12px",
18003
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
18004
+ }
18005
+ },
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,
18011
+ style: {
18012
+ background: "none",
18013
+ border: "none",
18014
+ color: "var(--dsw-alias-label-tertiary)",
18015
+ cursor: "pointer",
18016
+ fontSize: "18px",
18017
+ padding: "0 4px"
18018
+ },
18019
+ "aria-label": "Close preview"
18020
+ }, "\xD7")
18021
+ ),
18022
+ import_react17.default.createElement(
18023
+ "div",
18024
+ {
18025
+ style: { flex: 1, minHeight: 0, overflow: "auto", padding: "12px" }
18026
+ },
18027
+ previewLoading && import_react17.default.createElement("div", {
18028
+ style: { color: "var(--dsw-alias-label-tertiary)", padding: "16px 0" }
18029
+ }, "Loading..."),
18030
+ previewContent !== null && renderPreview(previewContent)
18031
+ )
18032
+ )
18033
+ )
18034
+ );
18035
+ }
18036
+ function renderOutputTree(entry, depth, targetId, onSelect, selected, selectedPath, selectedTargetId) {
18037
+ if (entry.kind === "directory" && entry.children !== void 0 && entry.children.length > 0) {
18038
+ const isSelectedTarget = selectedTargetId === targetId;
18039
+ const containsSelected = isSelectedTarget && entryContainsSelected(entry, selectedPath);
18040
+ return import_react17.default.createElement(FolderTreeRow, {
18041
+ key: `${targetId}:${entry.path}`,
18042
+ name: entry.name,
18043
+ depth,
18044
+ defaultOpen: depth < 2,
18045
+ containsSelected,
18046
+ children: entry.children.map(
18047
+ (child) => renderOutputTree(child, depth + 1, targetId, onSelect, selected, selectedPath, selectedTargetId)
18048
+ )
18049
+ });
18050
+ }
18051
+ const isSelected = selected?.entry.path === entry.path && selected?.targetId === targetId;
18052
+ return import_react17.default.createElement(FileRow, {
18053
+ key: `${targetId}:${entry.path}`,
18054
+ name: entry.name,
18055
+ depth,
18056
+ size: entry.size !== void 0 ? formatSize(entry.size) : void 0,
18057
+ isSelected,
18058
+ onClick: () => onSelect(entry, targetId)
18059
+ });
18060
+ }
18061
+ function renderPreview(result) {
18062
+ if (result.mime === "binary") {
18063
+ return import_react17.default.createElement(
18064
+ "div",
18065
+ {
18066
+ style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "13px", padding: "16px" }
18067
+ },
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)
18071
+ );
18072
+ }
18073
+ if (result.content === void 0) {
18074
+ return import_react17.default.createElement("div", {
18075
+ style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "13px" }
18076
+ }, "No content available");
18077
+ }
18078
+ if (result.mime === "markdown") {
18079
+ return import_react17.default.createElement(import_dsh_client_ui_primitives6.MarkdownText, { text: result.content });
18080
+ }
18081
+ if (result.mime === "json") {
18082
+ try {
18083
+ const parsed = JSON.parse(result.content);
18084
+ return import_react17.default.createElement(import_dsh_client_ui_primitives6.JsonTree, { data: parsed, label: result.name });
18085
+ } catch {
18086
+ }
18087
+ }
18088
+ const lines = result.content.split("\n");
18089
+ return import_react17.default.createElement(import_dsh_client_ui_primitives6.ReadBlock, {
18090
+ lines: lines.map((text, i) => ({ number: i + 1, text })),
18091
+ totalLines: lines.length,
18092
+ maxLines: 500
18093
+ });
18094
+ }
18095
+ function filterEntries(entries, allowed) {
18096
+ const result = [];
18097
+ for (const entry of entries) {
18098
+ if (entry.kind === "directory" && entry.children !== void 0) {
18099
+ const filtered = filterEntries(entry.children, allowed);
18100
+ if (filtered.length > 0) result.push({ ...entry, children: filtered });
18101
+ } else if (allowed.has(entry.category)) {
18102
+ result.push(entry);
18103
+ }
18104
+ }
18105
+ return result;
18106
+ }
18107
+ function formatSize(bytes) {
18108
+ if (bytes < 1024) return `${bytes} B`;
18109
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
18110
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
18111
+ }
18112
+
18113
+ // src/ui/client/pentester-view.tsx
18114
+ var CORE_REFRESH_MS = 1e4;
18115
+ var BACKOFF_BASE = 3e3;
18116
+ var BACKOFF_MAX = 3e4;
18117
+ function computeActivitySignature(sessionList, rootSessionId, delegationSessionIds) {
18118
+ const subagents = sessionList.subagentsByParent?.[rootSessionId];
18119
+ let running = 0;
18120
+ if (subagents !== void 0) {
18121
+ for (const entry of subagents.entries ?? []) {
18122
+ if (delegationSessionIds.has(entry.sessionId) && entry.activity === "running") {
18123
+ running++;
18124
+ }
18125
+ }
18126
+ }
18127
+ for (const sid of delegationSessionIds) {
18128
+ const s = sessionList.byId[sid];
18129
+ if (s !== void 0 && s.running) running++;
18130
+ }
18131
+ return { ids: [...delegationSessionIds].sort().join(","), running };
18132
+ }
18133
+ function resolveDelegationPresentationState(delegation, sessionList, rootSessionId) {
18134
+ if (delegation.status === "failed") return "inactive";
18135
+ if (delegation.status === "closed") return "inactive";
18136
+ const subagents = sessionList.subagentsByParent?.[rootSessionId];
18137
+ if (subagents !== void 0 && delegation.sessionId !== void 0) {
18138
+ const entry = subagents.entries?.find(
18139
+ (e) => e.sessionId === delegation.sessionId
18140
+ );
18141
+ if (entry !== void 0) {
18142
+ if (entry.activity === "running") return "running";
18143
+ if (entry.activity === "inactive") return "inactive";
18144
+ }
18145
+ }
18146
+ if (delegation.sessionId !== void 0) {
18147
+ const s = sessionList.byId[delegation.sessionId];
18148
+ if (s !== void 0 && s.running) return "running";
18149
+ }
18150
+ if (delegation.status === "starting") return "running";
18151
+ return "unknown";
18152
+ }
18153
+ function useDelegationActivity(sessions, rootSessionId, delegations) {
18154
+ const delegationSessionIds = (0, import_react18.useMemo)(
18155
+ () => new Set(delegations.filter((d) => d.sessionId !== void 0).map((d) => d.sessionId)),
18156
+ [delegations]
18157
+ );
18158
+ const prevSigRef = (0, import_react18.useRef)("");
18159
+ const cachedMapRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
18160
+ const getSnapshot = (0, import_react18.useCallback)(() => {
18161
+ const raw = sessions?.list.getSnapshot();
18162
+ if (raw === void 0) return cachedMapRef.current;
18163
+ const sig = computeActivitySignature(raw, rootSessionId, delegationSessionIds);
18164
+ const sigKey = `${sig.ids}|${sig.running}`;
18165
+ if (sigKey === prevSigRef.current) return cachedMapRef.current;
18166
+ prevSigRef.current = sigKey;
18167
+ const map2 = /* @__PURE__ */ new Map();
18168
+ for (const d of delegations) {
18169
+ map2.set(d.id, resolveDelegationPresentationState(d, raw, rootSessionId));
18170
+ }
18171
+ cachedMapRef.current = map2;
18172
+ return map2;
18173
+ }, [sessions, rootSessionId, delegationSessionIds, delegations]);
18174
+ return (0, import_react18.useSyncExternalStore)(
18175
+ sessions?.list.subscribe ?? (() => () => {
18176
+ }),
18177
+ getSnapshot
18178
+ );
18179
+ }
18180
+ var PERF_PREFIX = "[PENTERSTER_PERF]";
18181
+ var SWITCH_PREFIX = "[SESSION_SWITCH_PERF]";
18182
+ var RPC_PREFIX = "[PENTESTER_RPC]";
18183
+ var isPerf = typeof window !== "undefined" && window.__DSH_PENTESTER_PERF === true;
18184
+ function PentesterView(props) {
18185
+ const { sessionId, reflect, sessions, useSession, sessionSwitchTimes: sessionSwitchTimes2, sessionPerf } = props;
18186
+ const mountTime = (0, import_react18.useRef)(performance.now());
18187
+ const switchTime = sessionSwitchTimes2?.get(sessionId);
18188
+ if (isPerf) {
18189
+ console.log(`${PERF_PREFIX} mount=0ms`);
18190
+ if (switchTime !== void 0) {
18191
+ console.log(`${SWITCH_PREFIX} mount-delay=${(mountTime.current - switchTime).toFixed(0)}ms (current\u2192pentester-mount)`);
18192
+ }
18193
+ }
18194
+ const openState = useSession?.((s) => s.openState);
18195
+ const prevOpenState = (0, import_react18.useRef)(void 0);
18196
+ (0, import_react18.useEffect)(() => {
18197
+ if (!sessionPerf || openState === void 0) return;
18198
+ if (openState !== prevOpenState.current) {
18199
+ const elapsed = (performance.now() - mountTime.current).toFixed(0);
18200
+ console.log(
18201
+ `%c${SWITCH_PREFIX}%c openState %c${String(prevOpenState.current)}%c \u2192 %c${openState}%c +${elapsed}ms`,
18202
+ "color:#f59e0b;font-weight:bold",
18203
+ "",
18204
+ "color:#6b7280",
18205
+ "",
18206
+ "color:#10b981;font-weight:bold",
18207
+ ""
18208
+ );
18209
+ if (openState === "open" && switchTime !== void 0) {
18210
+ console.log(
18211
+ `%c${SWITCH_PREFIX}%c session-open %c+${(performance.now() - switchTime).toFixed(0)}ms%c (current\u2192open)`,
18212
+ "color:#f59e0b;font-weight:bold",
18213
+ "",
18214
+ "color:#10b981;font-weight:bold",
18215
+ "",
18216
+ ""
18217
+ );
18218
+ }
18219
+ prevOpenState.current = openState;
18220
+ }
18221
+ }, [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) => {
18236
+ const currentFace = reflect?.get("remote.pentesterView");
18237
+ return pentesterViewCommand(currentFace, input2, timeoutMs);
18238
+ }, [reflect]);
18239
+ const snapshot = dataState.kind === "bound" ? dataState.snapshot : null;
18240
+ const delegations = snapshot?.delegations ?? [];
18241
+ const liveActivity = useDelegationActivity(sessions, sessionId, delegations);
18242
+ const loadSnapshot = (0, import_react18.useCallback)(async (reason) => {
18243
+ if (reason !== "retry" && reason !== "refresh" && getPentesterEndpointState() === "missing") {
18244
+ if (isPerf) console.log(`${RPC_PREFIX} circuit-open endpoint=missing reason=${reason}`);
18245
+ return;
18246
+ }
18247
+ const mismatch = getBuildMismatch();
18248
+ if (mismatch !== null) {
18249
+ pollingPausedRef.current = true;
18250
+ setDataState({
18251
+ kind: "error",
18252
+ error: `Pentester Host/Client build mismatch
18253
+ Host: ${mismatch.host}
18254
+ Client: ${mismatch.client}`,
18255
+ code: "build_mismatch"
18256
+ });
18257
+ setRequestState("idle");
18258
+ return;
18259
+ }
18260
+ if (pendingRef.current !== null) {
18261
+ if (isPerf) console.log(`${RPC_PREFIX} busy reason=${reason} existing=${pendingRef.current.reason}`);
18262
+ return;
18263
+ }
18264
+ if (reason === "retry") {
18265
+ resetPentesterEndpointState();
18266
+ }
18267
+ const id = ++requestSeqRef.current;
18268
+ const pending = { id, sessionId, reason };
18269
+ pendingRef.current = pending;
18270
+ if (reason === "startup") {
18271
+ setRequestState("pending");
18272
+ } else if (reason === "retry") {
18273
+ setRequestState("retrying");
18274
+ }
18275
+ const tStart = performance.now();
18276
+ if (isPerf) {
18277
+ console.log(`${RPC_PREFIX} id=${id} kind=snapshot reason=${reason} start`);
18278
+ if (switchTime !== void 0) {
18279
+ console.log(`${SWITCH_PREFIX} pentesterView-request +${(tStart - switchTime).toFixed(0)}ms`);
18280
+ }
18281
+ }
18282
+ try {
18283
+ const result = await pentesterCommand({ kind: "snapshot", sessionId }, 3e4);
18284
+ if (pendingRef.current?.id !== id) {
18285
+ if (isPerf) console.log(`${RPC_PREFIX} id=${id} stale (current=${pendingRef.current?.id})`);
18286
+ return;
18287
+ }
18288
+ if (!mountedRef.current) return;
18289
+ const tResp = performance.now();
18290
+ if (isPerf) {
18291
+ console.log(`${RPC_PREFIX} id=${id} success duration=${(tResp - tStart).toFixed(0)}ms`);
18292
+ if (switchTime !== void 0) {
18293
+ console.log(`${SWITCH_PREFIX} pentesterView-response +${(tResp - switchTime).toFixed(0)}ms duration=${(tResp - tStart).toFixed(0)}ms`);
18294
+ }
18295
+ }
18296
+ if (result.ok) {
18297
+ backoffRef.current = 0;
18298
+ pollingPausedRef.current = false;
18299
+ const value = result.value;
18300
+ if (value !== null && typeof value === "object" && "kind" in value && value.kind === "unbound") {
18301
+ const unbound = value;
18302
+ setDataState({ kind: "unbound", targets: unbound.targets });
18303
+ snapshotRef.current = null;
18304
+ } else {
18305
+ const snap = value;
18306
+ snapshotRef.current = snap;
18307
+ setDataState({ kind: "bound", snapshot: snap });
18308
+ setActiveTargetId((prev) => {
18309
+ if (prev !== null) return prev;
18310
+ const targets = snap.targets;
18311
+ if (targets.length > 0) return targets[0].id;
18312
+ return null;
18313
+ });
18314
+ }
18315
+ setRequestState("idle");
18316
+ } else {
18317
+ if (result.code === "rpc_endpoint_missing") {
18318
+ pollingPausedRef.current = true;
18319
+ backoffRef.current = 0;
18320
+ if (isPerf) console.log(`${RPC_PREFIX} id=${id} fatal=${result.code} polling-paused`);
18321
+ } else {
18322
+ backoffRef.current = backoffRef.current === 0 ? BACKOFF_BASE : Math.min(backoffRef.current * 2, BACKOFF_MAX);
18323
+ }
18324
+ if (snapshotRef.current !== null) {
18325
+ setDataState({ kind: "error", error: result.error ?? "Unknown error", code: result.code });
18326
+ setRequestState("idle");
18327
+ } else {
18328
+ setDataState({ kind: "error", error: result.error ?? "Unknown error", code: result.code });
18329
+ setRequestState("idle");
18330
+ }
18331
+ }
18332
+ } catch (err) {
18333
+ if (pendingRef.current?.id !== id) {
18334
+ if (isPerf) console.log(`${RPC_PREFIX} id=${id} stale-error (current=${pendingRef.current?.id})`);
18335
+ return;
18336
+ }
18337
+ if (!mountedRef.current) return;
18338
+ const errMsg2 = err instanceof Error ? err.message : String(err);
18339
+ if (isPerf) console.log(`${RPC_PREFIX} id=${id} error=${errMsg2}`);
18340
+ if (errMsg2.includes("404") || errMsg2.includes("transport")) {
18341
+ pollingPausedRef.current = true;
18342
+ backoffRef.current = 0;
18343
+ } else {
18344
+ backoffRef.current = backoffRef.current === 0 ? BACKOFF_BASE : Math.min(backoffRef.current * 2, BACKOFF_MAX);
18345
+ }
18346
+ if (snapshotRef.current !== null) {
18347
+ setDataState({ kind: "error", error: errMsg2 });
18348
+ setRequestState("idle");
18349
+ } else {
18350
+ setDataState({ kind: "error", error: errMsg2 });
18351
+ setRequestState("idle");
18352
+ }
18353
+ } finally {
18354
+ if (pendingRef.current?.id === id) {
18355
+ pendingRef.current = null;
18356
+ firstLoadDone.current = true;
18357
+ }
18358
+ }
18359
+ }, [pentesterCommand, sessionId, switchTime]);
18360
+ (0, import_react18.useEffect)(() => {
18361
+ mountedRef.current = true;
18362
+ pollingPausedRef.current = false;
18363
+ backoffRef.current = 0;
18364
+ loadSnapshot("startup");
18365
+ const scheduleNext = () => {
18366
+ if (!mountedRef.current) return;
18367
+ if (pollingPausedRef.current) {
18368
+ if (isPerf) console.log(`${RPC_PREFIX} polling-paused endpoint-missing`);
18369
+ return;
18370
+ }
18371
+ const delay = backoffRef.current > 0 ? Math.min(backoffRef.current, BACKOFF_MAX) : CORE_REFRESH_MS;
18372
+ timerRef.current = setTimeout(async () => {
18373
+ if (!mountedRef.current) return;
18374
+ if (document.visibilityState !== "visible") {
18375
+ scheduleNext();
18376
+ return;
18377
+ }
18378
+ await loadSnapshot("poll");
18379
+ scheduleNext();
18380
+ }, delay);
18381
+ };
18382
+ scheduleNext();
18383
+ return () => {
18384
+ mountedRef.current = false;
18385
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
18386
+ };
18387
+ }, [loadSnapshot]);
18388
+ (0, import_react18.useEffect)(() => {
18389
+ const onVisible = () => {
18390
+ if (document.visibilityState === "visible" && mountedRef.current && firstLoadDone.current && !pollingPausedRef.current) {
18391
+ loadSnapshot("visible");
18392
+ }
18393
+ };
18394
+ document.addEventListener("visibilitychange", onVisible);
18395
+ return () => document.removeEventListener("visibilitychange", onVisible);
18396
+ }, [loadSnapshot]);
18397
+ const navigate = (0, import_react18.useCallback)((targetTab, focus) => {
18398
+ setTab(targetTab);
18399
+ if (focus !== void 0) setNavFocus(focus);
18400
+ }, []);
18401
+ const openSession = (0, import_react18.useCallback)((id) => {
18402
+ sessions?.open(id);
18403
+ }, [sessions]);
18404
+ const isInitialLoading = dataState.kind === "initial" && requestState === "pending";
18405
+ const isRetrying = requestState === "retrying";
18406
+ const errorMsg = dataState.kind === "error" ? dataState.error : null;
18407
+ const errorCode = dataState.kind === "error" ? dataState.code : null;
18408
+ const hasSnapshot = snapshot !== null;
18409
+ return import_react18.default.createElement(
18410
+ "div",
18411
+ {
18412
+ "data-testid": "pentester-view",
18413
+ "data-pentester-view": "",
18414
+ style: {
18415
+ display: "flex",
18416
+ flexDirection: "column",
18417
+ width: "100%",
18418
+ minHeight: "100%",
18419
+ overflow: "visible"
18420
+ }
18421
+ },
18422
+ // ── Layer 1: TabsBar (sticky) ────────────────────────────────────────
18423
+ import_react18.default.createElement(
18424
+ "div",
18425
+ {
18426
+ "data-testid": "pentester-tabs-bar",
18427
+ style: {
18428
+ flex: "none",
18429
+ position: "sticky",
18430
+ top: 0,
18431
+ zIndex: 29,
18432
+ background: "var(--dsw-alias-bg-base)",
18433
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
18434
+ padding: "10px 16px 0 16px",
18435
+ minHeight: "44px",
18436
+ boxSizing: "border-box"
18437
+ }
18438
+ },
18439
+ import_react18.default.createElement(PentesterTabBar, {
18440
+ items: PENTESTER_TABS.map((t) => ({
18441
+ id: t.id,
18442
+ label: t.id.charAt(0).toUpperCase() + t.id.slice(1)
18443
+ })),
18444
+ active: tab,
18445
+ onSelect: (id) => setTab(id)
18446
+ })
18447
+ ),
18448
+ // ── Refresh error banner ──────────────────────────────────────────────
18449
+ // Stale-while-revalidate: error + last good snapshot → show banner
18450
+ errorMsg !== null && hasSnapshot && import_react18.default.createElement(
18451
+ "div",
18452
+ {
18453
+ style: {
18454
+ flex: "none",
18455
+ padding: "6px 16px",
18456
+ background: "var(--dsw-bg-status-error, rgba(220, 38, 38, 0.1))",
18457
+ color: "var(--dsw-status-error, #dc2626)",
18458
+ fontSize: "12px",
18459
+ display: "flex",
18460
+ justifyContent: "space-between",
18461
+ alignItems: "center"
18462
+ }
18463
+ },
18464
+ import_react18.default.createElement("span", null, errorMsg),
18465
+ import_react18.default.createElement("button", {
18466
+ onClick: () => {
18467
+ if (pendingRef.current === null) {
18468
+ void loadSnapshot("retry");
18469
+ }
18470
+ },
18471
+ disabled: pendingRef.current !== null,
18472
+ style: {
18473
+ padding: "2px 8px",
18474
+ border: "none",
18475
+ borderRadius: "4px",
18476
+ background: "transparent",
18477
+ color: "var(--dsw-status-error, #dc2626)",
18478
+ cursor: pendingRef.current !== null ? "not-allowed" : "pointer",
18479
+ fontSize: "12px",
18480
+ textDecoration: "underline",
18481
+ opacity: pendingRef.current !== null ? 0.5 : 1
18482
+ }
18483
+ }, isRetrying ? "Retrying\u2026" : "Retry")
18484
+ ),
18485
+ // ── Body: flex 1, no scroll ──────────────────────────────────────────
18486
+ import_react18.default.createElement(
18487
+ "div",
18488
+ {
18489
+ style: {
18490
+ flex: 1,
18491
+ minHeight: 0,
18492
+ overflow: "visible",
18493
+ padding: "16px"
18494
+ }
18495
+ },
18496
+ // Initial loading: shell is already visible, show skeleton
18497
+ isInitialLoading && import_react18.default.createElement("div", {
18498
+ style: { padding: "24px", color: "var(--dsw-alias-label-tertiary)", fontSize: "14px" }
18499
+ }, "Loading..."),
18500
+ // Error state (no last good snapshot)
18501
+ dataState.kind === "error" && !hasSnapshot && import_react18.default.createElement(
18502
+ "div",
18503
+ { style: { padding: "24px" } },
18504
+ import_react18.default.createElement("div", {
18505
+ style: { color: "var(--dsw-status-error)", marginBottom: "12px", fontSize: "14px" }
18506
+ }, errorMsg ?? "Unknown error"),
18507
+ import_react18.default.createElement("button", {
18508
+ onClick: () => {
18509
+ if (pendingRef.current === null) {
18510
+ void loadSnapshot("retry");
18511
+ }
18512
+ },
18513
+ disabled: pendingRef.current !== null,
18514
+ style: {
18515
+ padding: "6px 16px",
18516
+ border: "1px solid var(--dsw-alias-border-l2)",
18517
+ borderRadius: "6px",
18518
+ background: "var(--dsw-alias-interactive-bg-hover)",
18519
+ color: "var(--dsw-alias-label-primary)",
18520
+ cursor: pendingRef.current !== null ? "not-allowed" : "pointer",
18521
+ fontSize: "13px",
18522
+ opacity: pendingRef.current !== null ? 0.5 : 1
18523
+ }
18524
+ }, isRetrying ? "Retrying\u2026" : "Retry")
18525
+ ),
18526
+ // Unbound state
18527
+ dataState.kind === "unbound" && import_react18.default.createElement(
18528
+ "div",
18529
+ { style: { padding: "24px" } },
18530
+ import_react18.default.createElement("div", {
18531
+ style: { fontSize: "14px", color: "var(--dsw-alias-label-tertiary)", marginBottom: "16px" }
18532
+ }, "This session is not bound to a pentest target."),
18533
+ dataState.targets.length > 0 && import_react18.default.createElement(
18534
+ "div",
18535
+ { style: { marginBottom: "12px" } },
18536
+ import_react18.default.createElement("div", {
18537
+ style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px", color: "var(--dsw-alias-label-primary)" }
18538
+ }, "Existing targets"),
18539
+ ...dataState.targets.map(
18540
+ (t) => import_react18.default.createElement(
18541
+ "div",
18542
+ {
18543
+ key: t.id,
18544
+ style: {
18545
+ display: "flex",
18546
+ alignItems: "center",
18547
+ justifyContent: "space-between",
18548
+ padding: "8px 12px",
18549
+ marginBottom: "4px",
18550
+ border: "1px solid var(--dsw-alias-border-l2)",
18551
+ borderRadius: "6px",
18552
+ fontSize: "13px"
18553
+ }
18554
+ },
18555
+ import_react18.default.createElement(
18556
+ "div",
18557
+ null,
18558
+ import_react18.default.createElement("div", { style: { fontWeight: 500 } }, t.target),
18559
+ import_react18.default.createElement(
18560
+ "div",
18561
+ { style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px" } },
18562
+ `${t.currentStage ?? "unknown"} \xB7 ${t.status}`
18563
+ )
18564
+ ),
18565
+ t.rootSessionId !== null && import_react18.default.createElement("button", {
18566
+ onClick: () => sessions?.open(t.rootSessionId),
18567
+ style: {
18568
+ padding: "4px 12px",
18569
+ border: "1px solid var(--dsw-alias-border-l2)",
18570
+ borderRadius: "4px",
18571
+ background: "var(--dsw-alias-interactive-bg-hover)",
18572
+ color: "var(--dsw-alias-label-primary)",
18573
+ cursor: "pointer",
18574
+ fontSize: "12px"
18575
+ }
18576
+ }, "Open Session")
18577
+ )
18578
+ )
18579
+ ),
18580
+ import_react18.default.createElement("button", {
18581
+ onClick: () => {
18582
+ if (pendingRef.current === null) {
18583
+ void loadSnapshot("refresh");
18584
+ }
18585
+ },
18586
+ disabled: pendingRef.current !== null,
18587
+ style: {
18588
+ padding: "6px 16px",
18589
+ border: "1px solid var(--dsw-alias-border-l2)",
18590
+ borderRadius: "6px",
18591
+ background: "var(--dsw-alias-interactive-bg-hover)",
18592
+ color: "var(--dsw-alias-label-primary)",
18593
+ cursor: pendingRef.current !== null ? "not-allowed" : "pointer",
18594
+ fontSize: "13px",
18595
+ marginTop: "8px",
18596
+ opacity: pendingRef.current !== null ? 0.5 : 1
18597
+ }
18598
+ }, "Refresh")
18599
+ ),
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, {
18618
+ snapshot,
18619
+ command: pentesterCommand,
18620
+ sessionId,
18621
+ onNavigate: navigate,
18622
+ focusFinding: navFocus.finding
18623
+ }),
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, {
18632
+ snapshot,
18633
+ command: pentesterCommand,
18634
+ sessionId,
18635
+ focusOutput: navFocus.output
18636
+ })
18637
+ )
18638
+ )
18639
+ );
18640
+ }
18641
+
18642
+ // src/ui/client/pentester/locales.ts
18643
+ var NS = "dsh-pentester-view";
18644
+ var zh = {
18645
+ "view.overview": "\u6982\u89C8",
18646
+ "view.stages": "\u9636\u6BB5",
18647
+ "view.findings": "\u6F0F\u6D1E",
18648
+ "view.trace": "\u8F68\u8FF9",
18649
+ "view.output": "\u8F93\u51FA",
18650
+ "status.completed": "\u5DF2\u5B8C\u6210",
18651
+ "status.active": "\u6D3B\u8DC3",
18652
+ "status.reviewing": "\u5F85\u5BA1\u6279",
18653
+ "status.pending": "\u7B49\u5F85\u4E2D",
18654
+ "status.blocked": "\u963B\u585E",
18655
+ "status.failed": "\u5931\u8D25",
18656
+ "overview.noRun": "\u5C1A\u65E0\u6E17\u900F\u6D4B\u8BD5\u8FD0\u884C\u3002\n\u5728 Chat \u4E2D\u5B8C\u6210 Pre-engagement \u5F00\u59CB\u3002",
18657
+ "overview.stats.assets": "\u8D44\u4EA7",
18658
+ "overview.stats.findings": "\u6F0F\u6D1E",
18659
+ "overview.stats.delegations": "\u59D4\u6258",
18660
+ "overview.currentWork": "\u5F53\u524D\u5DE5\u4F5C",
18661
+ "overview.stage": "\u9636\u6BB5",
18662
+ "stages.title": "PTES \u9636\u6BB5",
18663
+ "stages.noDelegations": "\u6682\u65E0\u59D4\u6258",
18664
+ "findings.empty": "\u6682\u672A\u53D1\u73B0\u6F0F\u6D1E",
18665
+ "findings.severity.critical": "\u4E25\u91CD",
18666
+ "findings.severity.high": "\u9AD8\u5371",
18667
+ "findings.severity.medium": "\u4E2D\u5371",
18668
+ "findings.severity.low": "\u4F4E\u5371",
18669
+ "findings.severity.info": "\u4FE1\u606F",
18670
+ "trace.empty": "\u6682\u65E0\u6267\u884C\u8F68\u8FF9",
18671
+ "output.empty": "\u6682\u65E0\u8F93\u51FA\u6587\u4EF6",
18672
+ "output.filter.all": "\u5168\u90E8",
18673
+ "output.filter.reports": "\u62A5\u544A",
18674
+ "output.filter.evidence": "\u8BC1\u636E",
18675
+ "output.filter.artifacts": "\u4EA7\u7269",
18676
+ "output.preview.unavailable": "\u65E0\u6CD5\u9884\u89C8",
18677
+ "output.preview.tooLarge": "\u6587\u4EF6\u8FC7\u5927",
18678
+ "error.load": "\u65E0\u6CD5\u52A0\u8F7D Pentester \u72B6\u6001",
18679
+ "error.retry": "\u91CD\u8BD5",
18680
+ "loading": "\u52A0\u8F7D\u4E2D..."
18681
+ };
18682
+ var en = {
18683
+ "view.overview": "Overview",
18684
+ "view.stages": "Stages",
18685
+ "view.findings": "Findings",
18686
+ "view.trace": "Trace",
18687
+ "view.output": "Output",
18688
+ "status.completed": "Completed",
18689
+ "status.active": "Active",
18690
+ "status.reviewing": "Pending approval",
18691
+ "status.pending": "Pending",
18692
+ "status.blocked": "Blocked",
18693
+ "status.failed": "Failed",
18694
+ "overview.noRun": "No pentest run yet.\nStart by completing Pre-engagement in Chat.",
18695
+ "overview.stats.assets": "Assets",
18696
+ "overview.stats.findings": "Findings",
18697
+ "overview.stats.delegations": "Delegations",
18698
+ "overview.currentWork": "Current Work",
18699
+ "overview.stage": "Stage",
18700
+ "stages.title": "PTES Stages",
18701
+ "stages.noDelegations": "No delegations",
18702
+ "findings.empty": "No findings yet",
18703
+ "findings.severity.critical": "Critical",
18704
+ "findings.severity.high": "High",
18705
+ "findings.severity.medium": "Medium",
18706
+ "findings.severity.low": "Low",
18707
+ "findings.severity.info": "Info",
18708
+ "trace.empty": "No execution trace yet",
18709
+ "output.empty": "No output files",
18710
+ "output.filter.all": "All",
18711
+ "output.filter.reports": "Reports",
18712
+ "output.filter.evidence": "Evidence",
18713
+ "output.filter.artifacts": "Artifacts",
18714
+ "output.preview.unavailable": "Preview unavailable",
18715
+ "output.preview.tooLarge": "File too large",
18716
+ "error.load": "Unable to load Pentester state",
18717
+ "error.retry": "Retry",
18718
+ "loading": "Loading..."
18719
+ };
18720
+
18721
+ // src/build-id.ts
18722
+ var BUILD_ID = "2.2.0+478786d";
18723
+
18724
+ // src/ui/client/plugin.tsx
18725
+ var SESSION_SWITCH_PERF = "[SESSION_SWITCH_PERF]";
18726
+ var isSessionPerf = typeof window !== "undefined" && window.__DSH_SESSION_PERF === true;
18727
+ var sessionSwitchTimes = /* @__PURE__ */ new Map();
18728
+ var lastCurrentId;
18729
+ var pentesterEndpointState2 = "unknown";
18730
+ function getPentesterEndpointState2() {
18731
+ return pentesterEndpointState2;
18732
+ }
18733
+ function setPentesterEndpointState(state) {
18734
+ pentesterEndpointState2 = state;
18735
+ }
18736
+ var buildMismatch = null;
18737
+ function getBuildMismatch() {
18738
+ return buildMismatch;
18739
+ }
18740
+ var remoteMounted = false;
18741
+ var remoteMountError = null;
18742
+ function isRemoteMounted() {
18743
+ return remoteMounted;
18744
+ }
18745
+ function getRemoteMountError() {
18746
+ return remoteMountError;
18747
+ }
18748
+ var inject = ["slots", "remote", "sessions", "locale"];
18749
+ function apply(ctx) {
18750
+ console.log(`[dsh-pentester] client build=${BUILD_ID}`);
18751
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "pentester: locale");
18752
+ ctx.effect(() => {
18753
+ let disposeMount;
18754
+ let disposed = false;
18755
+ void ctx.remote.$mount({
18756
+ package: "dsh-pentester",
18757
+ descriptors: [
18758
+ ...PENTESTER_REMOTE.descriptors,
18759
+ ...PENTESTER_VIEW_REMOTE.descriptors
18760
+ ]
18761
+ }).then((dispose) => {
18762
+ if (disposed) {
18763
+ dispose();
18764
+ return;
18765
+ }
18766
+ disposeMount = dispose;
18767
+ remoteMounted = true;
18768
+ remoteMountError = null;
18769
+ if (pentesterEndpointState2 === "missing") {
18770
+ pentesterEndpointState2 = "unknown";
18771
+ }
18772
+ if (isSessionPerf) {
18773
+ console.log("[dsh-pentester] client remote contribution mounted (pentesterDocker + pentesterView)");
18774
+ }
18775
+ void (async () => {
18776
+ try {
18777
+ const face = ctx.reflect.get("remote.pentesterView");
18778
+ if (face === void 0) return;
18779
+ const result = await face.command({ kind: "health" });
18780
+ if (result.ok && result.value !== null && typeof result.value === "object" && "ok" in result.value) {
18781
+ const inner = result.value.value;
18782
+ if (inner?.ready === true && typeof inner.buildId === "string") {
18783
+ if (inner.buildId === BUILD_ID) {
18784
+ buildMismatch = null;
18785
+ console.log(`[dsh-pentester] health check passed host=${inner.buildId} client=${BUILD_ID}`);
18786
+ } else {
18787
+ buildMismatch = { host: inner.buildId, client: BUILD_ID };
18788
+ console.error(`[dsh-pentester] BUILD MISMATCH host=${inner.buildId} client=${BUILD_ID}`);
18789
+ }
18790
+ }
18791
+ }
18792
+ } catch (error51) {
18793
+ console.warn("[dsh-pentester] build health check unavailable", error51);
18794
+ }
18795
+ })();
18796
+ }).catch((error51) => {
18797
+ remoteMounted = false;
18798
+ remoteMountError = error51 instanceof Error ? error51.message : String(error51);
18799
+ console.error("[dsh-pentester] client remote contribution mount failed:", remoteMountError);
18800
+ });
18801
+ return () => {
18802
+ disposed = true;
18803
+ disposeMount?.();
18804
+ };
18805
+ }, "pentester: remotes");
18806
+ ctx.slots.inject(
18807
+ "settings.section",
18808
+ () => ctx.slots.register(
18809
+ {
18810
+ name: "settings.section",
18811
+ id: "pentester-docker-engine",
18812
+ order: 90,
18813
+ label: () => "Pentester",
18814
+ inject: () => {
18815
+ const face = ctx.reflect.get("remote.pentesterDocker");
18816
+ return { command: (input2) => dockerCommand(face, input2) };
18817
+ }
18818
+ },
18819
+ (props) => import_react19.default.createElement(PentesterConfigSection, { ...props })
18820
+ )
18821
+ );
18822
+ ctx.slots.inject("conversation.view", () => {
18823
+ let disposeEntry;
18824
+ const sync = () => {
18825
+ const snapshot = ctx.sessions.list.getSnapshot();
18826
+ const current = snapshot.current;
18827
+ if (isSessionPerf && current !== lastCurrentId) {
18828
+ const now = performance.now();
18829
+ const prev = lastCurrentId;
18830
+ lastCurrentId = current;
18831
+ if (current !== void 0 && !sessionSwitchTimes.has(current)) {
18832
+ sessionSwitchTimes.set(current, now);
18833
+ }
18834
+ console.log(
18835
+ `%c${SESSION_SWITCH_PERF}%c current-changed %c${String(prev).slice(0, 8)}%c \u2192 %c${String(current).slice(0, 8)}%c +0ms`,
18836
+ "color:#f59e0b;font-weight:bold",
18837
+ "",
18838
+ "color:#6b7280",
18839
+ "",
18840
+ "color:#3b82f6;font-weight:bold",
18841
+ ""
18842
+ );
18843
+ }
18844
+ if (current !== void 0 && isPentesterRootSession(snapshot, current)) {
18845
+ if (disposeEntry === void 0) {
18846
+ disposeEntry = ctx.slots.register(
18847
+ {
18848
+ name: "conversation.view",
18849
+ id: "pentester",
18850
+ order: 20,
18851
+ label: "Pentester",
18852
+ inject: (sessionId) => {
18853
+ return {
18854
+ sessionId,
18855
+ reflect: ctx.reflect,
18856
+ sessions: {
18857
+ open: (id) => ctx.sessions.open(id),
18858
+ list: ctx.sessions.list
18859
+ },
18860
+ sessionSwitchTimes,
18861
+ sessionPerf: isSessionPerf
18862
+ };
18863
+ }
18864
+ },
18865
+ (props) => {
18866
+ return import_react19.default.createElement(PentesterView, {
18867
+ sessionId: props.sessionId,
18868
+ reflect: props.reflect,
18869
+ sessions: props.sessions,
18870
+ useSession: props.useSession,
18871
+ sessionSwitchTimes: props.sessionSwitchTimes,
18872
+ sessionPerf: props.sessionPerf
18873
+ });
18874
+ }
15218
18875
  );
15219
18876
  }
15220
18877
  } else {