@dsh-fly/dsh-plugin-fly-engagement 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/client.js +400 -0
  2. package/package.json +3 -2
package/lib/client.js ADDED
@@ -0,0 +1,400 @@
1
+ /* @dsh-fly/dsh-plugin-fly-engagement — browser half, built by scripts/build-plugin-client.mjs.
2
+ * src-hash: dfc489bb58d29d86b67f417b96d83c86f598b665a6426ab841f69745bd106120
3
+ * Factory-form CJS: the loader calls the factory with its platform `require`
4
+ * and memoizes the returned exports. Keep the banner so the loader id and the
5
+ * package that produced the bundle stay greppable in a live page. */
6
+ window.__ModuleLoader__.load({
7
+ id: "@dsh-fly/dsh-plugin-fly-engagement",
8
+ factory: (require) => {
9
+ var module = { exports: {} };
10
+ var exports = module.exports;
11
+ "use strict";
12
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
13
+ const jsxRuntime = require("react/jsx-runtime");
14
+ const react = require("react");
15
+ const name = "fly-engagement-panel";
16
+ const inject = ["slots", "sidebarRightTabs"];
17
+ const PACKAGE = "@dsh-fly/dsh-plugin-fly-engagement";
18
+ const KIND = "fly.engagement";
19
+ const TAB_TITLE = "Engagement";
20
+ const TAB_DESCRIPTION = "目标 / 资产 / 漏洞 / 证据图";
21
+ const GUIDE_ORDER = 12;
22
+ const BASE = "/fly/engagement";
23
+ const STORE_KEY = "fly.engagement.workspace";
24
+ const PANEL_CSS = `/*
25
+ * fly-engagement 抽屉面板 —— **目标效果图的样式表**(2026-10-18,等用户判方向)
26
+ *
27
+ * 这张表就是实现时的对照物:类名 / token / 尺寸都按真面板的约定写('.fly-eg-*' 前缀,
28
+ * 与 shell 的 '.fly-*' 一族同形),所以"照图实现"不会漂移成另一套。
29
+ *
30
+ * token 纪律:这里因为要**独立渲染**(没有宿主 skin),把深色底值写进 ':root' 兜底;
31
+ * 实现时一律 'var(--dsw-alias-*, 兜底)' —— 真宿主会给出 token,兜底只在 mock/降级时生效。
32
+ * 面板框(rail 8px / desk 12px / 3px 亮缘)照 '.fly-panel' 的结构,不再发明第二套。
33
+ */
34
+ :root {
35
+ --dsw-alias-bg-base: #070b12;
36
+ --dsw-alias-bg-layer-1: rgba(255, 255, 255, 0.045);
37
+ --dsw-alias-bg-layer-2: rgba(255, 255, 255, 0.07);
38
+ --dsw-alias-fg-primary: #e8eef8;
39
+ --dsw-alias-fg-secondary: #9fb0c6;
40
+ --dsw-alias-fg-muted: #6b7d95;
41
+ --dsw-alias-border-l1: rgba(255, 255, 255, 0.10);
42
+ --fly-accent: #f0a55a;
43
+ --fly-accent-dim: rgba(240, 165, 90, 0.16);
44
+ --fly-critical: #ff5f6d;
45
+ --fly-high: #ff9b52;
46
+ --fly-medium: #e8c54a;
47
+ --fly-low: #6fc2ff;
48
+ --fly-info: #8fa3bd;
49
+ }
50
+
51
+ * { box-sizing: border-box; }
52
+ body {
53
+ margin: 0;
54
+ padding: var(--fly-desk, 12px);
55
+ background: #05080d;
56
+ font: 13px/1.5 -apple-system, "SF Pro Text", "PingFang SC", system-ui, sans-serif;
57
+ color: var(--dsw-alias-fg-primary);
58
+ }
59
+
60
+ /* ── 面板框:照 .fly-panel 的结构(rail 8px / 3px 亮缘 / 底部关系型亮缘) ── */
61
+ .fly-eg-panel {
62
+ --fly-rail: 8px;
63
+ width: 760px;
64
+ border-radius: 12px;
65
+ border: 1px solid var(--dsw-alias-border-l1);
66
+ background:
67
+ radial-gradient(900px 320px at 12% -12%, rgba(240, 165, 90, 0.10), transparent 70%),
68
+ linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0) 42%),
69
+ var(--dsw-alias-bg-base);
70
+ padding: var(--fly-rail) calc(var(--fly-rail) + 6px) calc(var(--fly-rail) + 8px);
71
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
72
+ }
73
+
74
+ /* ── 头部:工作区 + goal + authorization ── */
75
+ .fly-eg-head { display: flex; flex-direction: column; gap: 6px; padding-bottom: 10px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
76
+ .fly-eg-title { display: flex; align-items: baseline; gap: 8px; }
77
+ .fly-eg-title h1 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: 0.2px; }
78
+ .fly-eg-badge { font-size: 11px; padding: 1px 7px; border-radius: 999px; background: var(--fly-accent-dim); color: var(--fly-accent); border: 1px solid rgba(240, 165, 90, 0.35); }
79
+ .fly-eg-meta { font-size: 11.5px; color: var(--dsw-alias-fg-secondary); display: flex; flex-wrap: wrap; gap: 10px; }
80
+ .fly-eg-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--dsw-alias-fg-primary); }
81
+ .fly-eg-auth { font-size: 11.5px; color: var(--dsw-alias-fg-muted); }
82
+ .fly-eg-auth b { color: var(--fly-accent); font-weight: 600; }
83
+
84
+ /* ── 计数读数:counts + dropped/refused/truncated(截断显眼) ── */
85
+ .fly-eg-counters { display: flex; gap: 4px; margin: 8px 0; flex-wrap: wrap; align-items: center; }
86
+ .fly-eg-counter { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); border-radius: 6px; padding: 2px 6px; font-size: 10.5px; line-height: 1.5; color: var(--dsw-alias-fg-muted); }
87
+ .fly-eg-counter b { margin-left: 3px; font-size: 11px; }
88
+ .fly-eg-counter b { color: var(--dsw-alias-fg-primary); font-weight: 600; margin-left: 5px; }
89
+ .fly-eg-counter.is-warn { border-color: rgba(255, 95, 109, 0.45); background: rgba(255, 95, 109, 0.10); color: #ffb8be; }
90
+ .fly-eg-counter.is-warn b { color: #ffd7da; }
91
+
92
+ /* ── 表/图 切换(借 dsh-pentest AssetsView 的两模式) ── */
93
+ .fly-eg-tabs { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); margin-bottom: 10px; }
94
+ .fly-eg-tab { font-size: 11.5px; padding: 3px 10px; border-radius: 6px; color: var(--dsw-alias-fg-secondary); }
95
+ .fly-eg-tab.is-active { background: rgba(240, 165, 90, 0.18); color: var(--fly-accent); }
96
+
97
+ /* ── 分区标题 ── */
98
+ .fly-eg-section { margin-top: 12px; }
99
+ .fly-eg-section > h2 { margin: 0 0 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-fg-secondary); letter-spacing: 0.4px; text-transform: uppercase; }
100
+
101
+ /* ── 表 ── */
102
+ .fly-eg-table { width: 100%; border-collapse: collapse; font-size: 12px; }
103
+ .fly-eg-table th { text-align: left; font-weight: 500; color: var(--dsw-alias-fg-muted); font-size: 11px; padding: 4px 6px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
104
+ .fly-eg-table td { padding: 5px 6px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); vertical-align: top; }
105
+ .fly-eg-id { font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: var(--dsw-alias-fg-muted); }
106
+ .fly-eg-src { font-size: 11px; color: var(--dsw-alias-fg-muted); }
107
+ .fly-eg-src b { color: var(--dsw-alias-fg-secondary); font-weight: 500; }
108
+ .fly-eg-sev { font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 999px; display: inline-block; }
109
+ .fly-eg-sev.critical { color: #fff; background: var(--fly-critical); }
110
+ .fly-eg-sev.high { color: #1a0f06; background: var(--fly-high); }
111
+ .fly-eg-sev.medium { color: #1a1506; background: var(--fly-medium); }
112
+ .fly-eg-sev.low { color: #061019; background: var(--fly-low); }
113
+ .fly-eg-sev.info { color: #0b1017; background: var(--fly-info); }
114
+ .fly-eg-steps { margin: 4px 0 0 0; padding-left: 18px; color: var(--dsw-alias-fg-secondary); font-size: 11.5px; }
115
+ .fly-eg-steps code, .fly-eg-cmd { font-family: ui-monospace, Menlo, monospace; font-size: 11px; background: rgba(255, 255, 255, 0.06); padding: 0 4px; border-radius: 4px; }
116
+
117
+ /* ── 图区:静态分层 + 边胶囊 + 只读(借 xyflow 的形状,不引入拖拽) ── */
118
+ .fly-eg-graph { position: relative; height: 300px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; background: radial-gradient(600px 240px at 50% 0%, rgba(255,255,255,0.035), transparent 70%), var(--dsw-alias-bg-layer-1); overflow: hidden; }
119
+ .fly-eg-graph-hint { position: absolute; left: 8px; bottom: 6px; font-size: 10.5px; color: var(--dsw-alias-fg-muted); }
120
+ .fly-eg-graph-badge { position: absolute; right: 8px; top: 8px; font-size: 10.5px; padding: 2px 7px; border-radius: 999px; border: 1px solid rgba(255, 95, 109, 0.45); background: rgba(255, 95, 109, 0.12); color: #ffb8be; }
121
+ .fly-eg-node { position: absolute; transform: translate(-50%, -50%); border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); padding: 5px 9px; font-size: 11.5px; white-space: nowrap; }
122
+ .fly-eg-node.is-finding { border-color: rgba(255, 155, 82, 0.55); background: rgba(255, 155, 82, 0.14); color: #ffd7b0; }
123
+ .fly-eg-node.is-asset { border-color: rgba(111, 194, 255, 0.45); background: rgba(111, 194, 255, 0.12); color: #cbe8ff; }
124
+ .fly-eg-edge { position: absolute; height: 1.5px; background: rgba(240, 165, 90, 0.55); transform-origin: 0 50%; }
125
+ .fly-eg-edge-cap { position: absolute; font-size: 10px; color: var(--fly-accent); background: rgba(7, 11, 18, 0.85); padding: 0 4px; border-radius: 4px; border: 1px solid rgba(240, 165, 90, 0.3); }
126
+
127
+ /* ── 报告区 ── */
128
+ .fly-eg-report { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); padding: 8px 10px; font-size: 11.5px; color: var(--dsw-alias-fg-secondary); }
129
+ .fly-eg-report-actions { display: flex; gap: 6px; margin-top: 8px; }
130
+ .fly-eg-btn { font-size: 11.5px; padding: 3px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-fg-primary); }
131
+
132
+ /* 图区的线走 SVG(按节点实测中心算),不用手摆坐标 —— 实现时同样按数据算 */
133
+ .fly-eg-wires { position: absolute; inset: 0; pointer-events: none; }
134
+
135
+ /* 连线走**折线**(elbow):横向只占层间那一段,纵向在间隙里走 —— 比直线省横向空间。
136
+ 实现时同样用 SVG path(M/H/V 折角 + stroke-linejoin:round)。 */
137
+ .fly-eg-wire { fill: none; stroke: rgba(240, 165, 90, 0.55); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
138
+
139
+ /* ── 事实按来源工具折叠(<details>,缺省收起:长会话省地方) ── */
140
+ .fly-eg-group { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); margin-bottom: 4px; }
141
+ .fly-eg-group > summary { cursor: default; list-style: none; padding: 4px 8px; font-size: 11.5px; color: var(--dsw-alias-fg-secondary); display: flex; align-items: center; gap: 6px; }
142
+ .fly-eg-group > summary::-webkit-details-marker { display: none; }
143
+ .fly-eg-group > summary::before { content: '▸'; color: var(--dsw-alias-fg-muted); font-size: 10px; }
144
+ .fly-eg-group[open] > summary::before { content: '▾'; }
145
+ .fly-eg-group > summary b { color: var(--dsw-alias-fg-primary); font-weight: 600; }
146
+ .fly-eg-group > summary .fly-eg-count { margin-left: auto; font-size: 10.5px; color: var(--dsw-alias-fg-muted); }
147
+ .fly-eg-group table { margin: 0 0 2px; }
148
+
149
+ .fly-eg-input { width: 100%; font: inherit; font-size: 11.5px; padding: 3px 8px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-fg-primary); }
150
+ .fly-eg-error { margin: 8px 0; padding: 6px 9px; border-radius: 8px; border: 1px solid rgba(255,95,109,0.45); background: rgba(255,95,109,0.10); color: #ffb8be; font-size: 11.5px; }
151
+ .fly-eg-empty { margin: 14px 0; color: var(--dsw-alias-fg-muted); font-size: 11.5px; }
152
+ `;
153
+ async function getJson(path) {
154
+ const response = await fetch(path, { headers: { accept: "application/json" } });
155
+ if (!response.ok) throw new Error(path + " → HTTP " + response.status);
156
+ return await response.json();
157
+ }
158
+ function Graph({ nodes, edges, kind }) {
159
+ const byLayer = /* @__PURE__ */ new Map();
160
+ for (const node of nodes) {
161
+ const list = byLayer.get(node.layer) ?? [];
162
+ list.push(node);
163
+ byLayer.set(node.layer, list);
164
+ }
165
+ const layers = [...byLayer.keys()].sort((a, b) => a - b);
166
+ const widest = Math.max(120, ...nodes.map((node) => node.label.length * 6.6 + 20));
167
+ const gap = 78;
168
+ const xOf = /* @__PURE__ */ new Map();
169
+ let cursor = 60;
170
+ for (const layer of layers) {
171
+ for (const node of byLayer.get(layer) ?? []) xOf.set(node.id, cursor + widest / 2);
172
+ cursor += widest + gap;
173
+ }
174
+ const height = Math.max(180, ...layers.map((layer) => (byLayer.get(layer) ?? []).length * 54));
175
+ const yOf = /* @__PURE__ */ new Map();
176
+ for (const layer of layers) {
177
+ const list = byLayer.get(layer) ?? [];
178
+ list.forEach((node, index) => yOf.set(node.id, height / (list.length + 1) * (index + 1)));
179
+ }
180
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-graph", style: { height: height + 60 }, children: [
181
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "fly-eg-wires", width: "100%", height: "100%", children: edges.filter((edge) => xOf.has(edge.from) && xOf.has(edge.to)).map((edge) => {
182
+ const x1 = (xOf.get(edge.from) ?? 0) + widest / 2;
183
+ const x2 = (xOf.get(edge.to) ?? 0) - widest / 2;
184
+ const midX = (x1 + x2) / 2;
185
+ return /* @__PURE__ */ jsxRuntime.jsx("path", { className: "fly-eg-wire", d: "M " + x1 + " " + yOf.get(edge.from) + " H " + midX + " V " + yOf.get(edge.to) + " H " + x2 }, kind + edge.from + edge.to);
186
+ }) }),
187
+ nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
188
+ "div",
189
+ {
190
+ className: "fly-eg-node" + (node.tone === "finding" ? " is-finding" : node.tone === "asset" ? " is-asset" : ""),
191
+ style: { left: xOf.get(node.id), top: yOf.get(node.id) },
192
+ children: node.label
193
+ },
194
+ node.id
195
+ )),
196
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-graph-hint", children: "折线走层间间隙 · 静态分层 · 只读" })
197
+ ] });
198
+ }
199
+ function Panel() {
200
+ const [workspace, setWorkspace] = react.useState(() => localStorage.getItem(STORE_KEY) ?? "");
201
+ const [state, setState] = react.useState(null);
202
+ const [error, setError] = react.useState("");
203
+ const [view, setView] = react.useState("table");
204
+ const [report, setReport] = react.useState("");
205
+ const load = react.useCallback(async (target) => {
206
+ if (target.trim() === "") return;
207
+ setError("");
208
+ try {
209
+ const [next, rendered] = await Promise.all([
210
+ getJson(BASE + "/state?workspace=" + encodeURIComponent(target)),
211
+ getJson(BASE + "/report?workspace=" + encodeURIComponent(target)).catch(() => ({ markdown: "" }))
212
+ ]);
213
+ setState(next);
214
+ setReport(rendered.markdown);
215
+ localStorage.setItem(STORE_KEY, target);
216
+ } catch (failure) {
217
+ setError(String(failure.message ?? failure));
218
+ setState(null);
219
+ }
220
+ }, []);
221
+ react.useEffect(() => {
222
+ void load(workspace);
223
+ }, [load, workspace]);
224
+ const projection = state?.projection;
225
+ const groups = react.useMemo(() => {
226
+ const map = /* @__PURE__ */ new Map();
227
+ for (const fact of projection?.facts ?? []) {
228
+ const tool = fact.source?.tool ?? "(unknown tool)";
229
+ map.set(tool, [...map.get(tool) ?? [], fact]);
230
+ }
231
+ return [...map.entries()].sort((a, b) => b[1].length - a[1].length);
232
+ }, [projection]);
233
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "fly-eg-panel", children: [
234
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-head", children: [
235
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-title", children: [
236
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Engagement" }),
237
+ projection?.goal === null || projection === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-badge", children: projection.goal.target })
238
+ ] }),
239
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-meta", children: /* @__PURE__ */ jsxRuntime.jsx(
240
+ "input",
241
+ {
242
+ className: "fly-eg-input",
243
+ value: workspace,
244
+ placeholder: "工作区绝对路径(记录在 <workspace>/.fly-engagement/events.jsonl)",
245
+ onChange: (event) => setWorkspace(event.target.value),
246
+ onKeyDown: (event) => {
247
+ if (event.key === "Enter") void load(workspace);
248
+ }
249
+ }
250
+ ) }),
251
+ projection?.goal === null || projection === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
252
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-meta", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
253
+ "objective: ",
254
+ projection.goal.objective
255
+ ] }) }),
256
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-auth", children: [
257
+ "authorization: ",
258
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.goal.authorization === void 0 || projection.goal.authorization === "" ? "(未记录)" : projection.goal.authorization }),
259
+ " · 审计事实,不是门禁"
260
+ ] })
261
+ ] })
262
+ ] }),
263
+ error !== "" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-error", children: error }) : null,
264
+ projection === void 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-empty", children: "还没读取记录(填工作区后回车)" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
265
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-counters", children: [
266
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
267
+ "facts",
268
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.facts })
269
+ ] }),
270
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
271
+ "findings",
272
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.findings })
273
+ ] }),
274
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
275
+ "assets",
276
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.assets })
277
+ ] }),
278
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
279
+ "edges",
280
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.edges })
281
+ ] }),
282
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter" + (projection.dropped > 0 ? " is-warn" : ""), children: [
283
+ "dropped",
284
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.dropped })
285
+ ] }),
286
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter" + (projection.refused > 0 ? " is-warn" : ""), children: [
287
+ "refused",
288
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.refused })
289
+ ] })
290
+ ] }),
291
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-tabs", children: [
292
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-tab" + (view === "table" ? " is-active" : ""), onClick: () => setView("table"), children: "表" }),
293
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-tab" + (view === "graph" ? " is-active" : ""), onClick: () => setView("graph"), children: "图" })
294
+ ] }),
295
+ view === "table" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
296
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
297
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Assets" }),
298
+ /* @__PURE__ */ jsxRuntime.jsx("table", { className: "fly-eg-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: projection.assets.map((asset) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
299
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-id", children: asset.id }),
300
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: asset.type }),
301
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: asset.value }),
302
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-src", children: asset.parentId ?? "—" })
303
+ ] }, asset.id)) }) })
304
+ ] }),
305
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
306
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Findings" }),
307
+ /* @__PURE__ */ jsxRuntime.jsx("table", { className: "fly-eg-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: projection.findings.map((finding) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
308
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-id", children: finding.id }),
309
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-sev " + (finding.severity ?? "info"), children: finding.severity ?? "info" }) }),
310
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: finding.title }),
311
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "fly-eg-steps", children: finding.reproducibleSteps.map((step, index) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: step }) }, index)) }) })
312
+ ] }, finding.id)) }) })
313
+ ] }),
314
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
315
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Facts · by source tool" }),
316
+ groups.map(([tool, facts], index) => /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "fly-eg-group", open: index === 0, children: [
317
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { children: [
318
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: tool }),
319
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-count", children: [
320
+ facts.length,
321
+ " 条"
322
+ ] })
323
+ ] }),
324
+ /* @__PURE__ */ jsxRuntime.jsx("table", { className: "fly-eg-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: facts.map((fact) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
325
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-id", children: fact.id }),
326
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: fact.detail })
327
+ ] }, fact.id)) }) })
328
+ ] }, tool))
329
+ ] }),
330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
331
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Report" }),
332
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-report", children: report === "" ? "(还没有可渲染的报告)" : report.slice(0, 2e3) })
333
+ ] })
334
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
335
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
336
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Evidence graph" }),
337
+ /* @__PURE__ */ jsxRuntime.jsx(
338
+ Graph,
339
+ {
340
+ kind: "proves",
341
+ nodes: [
342
+ ...projection.facts.map((fact, index) => ({ id: fact.id, label: fact.id + " " + (fact.kind ?? "info"), layer: 0, tone: void 0 })),
343
+ ...projection.findings.map((finding) => ({ id: finding.id, label: finding.id + " " + finding.title, layer: 1, tone: "finding" }))
344
+ ],
345
+ edges: projection.edges.filter((edge) => edge.kind === "proves" || edge.kind === "derived_from")
346
+ }
347
+ )
348
+ ] }),
349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Asset tree" }),
351
+ /* @__PURE__ */ jsxRuntime.jsx(
352
+ Graph,
353
+ {
354
+ kind: "parent",
355
+ nodes: projection.assets.map((asset) => ({ id: asset.id, label: asset.value + " (" + asset.type + ")", layer: asset.parentId === void 0 ? 0 : 1, tone: "asset" })),
356
+ edges: projection.edges.filter((edge) => edge.kind === "parent")
357
+ }
358
+ )
359
+ ] })
360
+ ] })
361
+ ] })
362
+ ] });
363
+ }
364
+ function EngagementMark({ size = 18, active = false }) {
365
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "fly-mark", "data-active": active ? "true" : "false", width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
366
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "6", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" }),
367
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "18", cy: "12", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" }),
368
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "18", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" }),
369
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 7l8 4M8 17l8-4", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
370
+ ] });
371
+ }
372
+ function apply(ctx) {
373
+ ctx.effect(() => ctx.sidebarRightTabs.register({
374
+ id: PACKAGE,
375
+ kind: KIND,
376
+ title: () => TAB_TITLE,
377
+ guide: [{ id: "open", order: GUIDE_ORDER, title: () => TAB_TITLE, description: () => TAB_DESCRIPTION, icon: EngagementMark }]
378
+ }), "fly-engagement-panel: drawer type");
379
+ ctx.effect(() => {
380
+ const tag = document.createElement("style");
381
+ tag.setAttribute("data-plugin", PACKAGE);
382
+ tag.textContent = PANEL_CSS;
383
+ document.head.appendChild(tag);
384
+ return () => {
385
+ tag.remove();
386
+ };
387
+ }, "fly-engagement-panel: panel stylesheet");
388
+ ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register(
389
+ { name: "sidebar.right.pane.tab", key: PACKAGE, inject: () => ({}) },
390
+ Panel
391
+ )), "fly-engagement-panel: drawer body");
392
+ }
393
+ exports.EngagementMark = EngagementMark;
394
+ exports.apply = apply;
395
+ exports.inject = inject;
396
+ exports.name = name;
397
+
398
+ return module.exports;
399
+ },
400
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-fly/dsh-plugin-fly-engagement",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -45,7 +45,8 @@
45
45
  "scripts": {
46
46
  "verify": "node scripts/verify.mjs",
47
47
  "test": "node --test tests/*.spec.mjs",
48
- "build:client": "node scripts/build-client.mjs"
48
+ "build:client": "node scripts/build-client.mjs",
49
+ "prepare": "node scripts/build-client.mjs"
49
50
  },
50
51
  "description": "DSH Fly engagement 记录面:eng_record/eng_state/eng_report + 抽屉面板(工作区 JSONL + 投影)",
51
52
  "homepage": "https://github.com/ethanchen669/dsh-plugin-fly-engagement#readme",