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