New:Socket for Asana Is Now Available.Learn more
Get Started

dsh-decision-split

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dsh-decision-split - npm Package Compare versions

Comparing version
0.1.9
to
0.2.2
+57
-166
lib/client.js

@@ -18,17 +18,4 @@ window.__ModuleLoader__.load({

".ddec-side-empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}",
".ddec-card{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px;padding:12px 16px;margin:8px 0}",
".ddec-card-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}",
".ddec-card-title{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}",
".ddec-summary{max-height:340px;overflow:auto;margin-bottom:10px}",
".ddec-md.ddec-empty-text{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}",
".ddec-textarea{width:100%;min-height:72px;resize:vertical;box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 12px;font-family:var(--dsw-font-family);font-size:14px;line-height:22px}",
".ddec-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}",
".ddec-textarea:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}",
".ddec-foot{display:flex;justify-content:flex-end;margin-top:10px}",
".ddec-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:none;border-radius:14px;cursor:pointer;font-size:12px;line-height:18px;padding:0 10px;height:28px;font-family:var(--dsw-font-family)}",
".ddec-btn-primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}",
".ddec-btn-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}",
".ddec-btn-ghost{background:transparent;color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2)}",
".ddec-btn-ghost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
".ddec-btn:disabled{cursor:not-allowed;opacity:.4}",
".ddec-open{display:inline-flex;align-items:center;justify-content:center;gap:4px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;cursor:pointer;font-size:12px;line-height:18px;padding:0 10px;height:28px;font-family:var(--dsw-font-family);margin-bottom:6px}",
".ddec-open:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
".ddec-md{min-width:0;overflow-wrap:anywhere;font:var(--dsw-font-markdown-base);color:var(--dsw-alias-label-primary)}",

@@ -72,4 +59,2 @@ ".ddec-md strong{font-weight:600}",

var MARKER = /^(#{1,6})\s+(决策摘要|决策要点|待决策清单|决策清单|决策事项|方案对比|方案选择|需要决策|待决策|待确认|待拍板|请你决定|请拍板|决策项|决策点|Decision\s+Summary|Decision\s+Points|Decision\s+Required|Decision\s+Needed|Please\s+Decide|Please\s+Choose|Need\s+Your\s+Decision|Options|Choices)[::]?\s*$/im;
function textOf(node) {

@@ -84,16 +69,2 @@ if (node === undefined || node === null || !Array.isArray(node.blocks)) return "";

function summaryOf(text) {
var m = text.match(MARKER);
if (m === null) return null;
var level = m[1].length;
var lines = text.slice(m.index).split("\n");
var kept = [];
for (var i = 1; i < lines.length; i++) {
var hh = lines[i].match(/^(#{1,6})\s/);
if (hh && hh[1].length <= level) break;
kept.push(lines[i]);
}
return kept.join("\n").trim();
}
function titleOf(text) {

@@ -112,81 +83,2 @@ var lines = text.replace(/\r\n?/g, "\n").split("\n");

// 语义判定:只有内容确实呈现「多个决策点」或「多个可选方案」时才触发,
// 而不是看标题存在与否,也不是看内容长短。判定只依赖内容本身。
function hasCompareTable(text) {
var lines = text.split("\n");
for (var i = 0; i < lines.length - 1; i++) {
if (/^\s*\|[^\n]+\|\s*$/.test(lines[i]) && /^\s*\|?[\s:\-|]+\|?\s*$/.test(lines[i + 1]) && lines[i + 1].indexOf("-") !== -1) {
if (!/方案|选项|备选|对比|选择/.test(lines[i])) continue;
var rows = 0;
for (var j = i + 2; j < lines.length; j++) {
var row = lines[j].trim();
if (row === "" || row.charAt(0) !== "|") break;
rows++;
}
if (rows >= 2) return true;
}
}
return false;
}
function decisionPointCount(text) {
var m = text.match(/\*{0,2}\s*(?:决策|决策点|待决策|需决策|待确认)[^::]{0,6}[::]/g);
return m === null ? 0 : m.length;
}
function alternatives(text) {
var seen = {};
var re = /(?:方案|选项|备选)\s*([A-Za-z一二三四五六七八九十0-9]+)/g;
var m;
while ((m = re.exec(text)) !== null) {
if (seen[m[0]] === undefined) seen[m[0]] = true;
}
return Object.keys(seen).length;
}
function needsDecision(text) {
if (typeof text !== "string") return false;
text = text.replace(/\r\n?/g, "\n");
if (decisionPointCount(text) >= 2) return true;
if (alternatives(text) >= 2) return true;
if (hasCompareTable(text)) return true;
return false;
}
// 无决策段标题时,把命中的决策点/方案条目抽取成列表,便于逐个阅读拍板。
function decisionSummary(text) {
text = text.replace(/\r\n?/g, "\n");
var lines = text.split("\n");
var out = [];
var seen = {};
var re = /(?:方案|选项|备选|决策|决策点|待决策|待确认)\s*[A-Za-z一二三四五六七八九十0-9]+\s*[::]|(?:方案|选项|备选)\s*[A-Za-z一二三四五六七八九十0-9]+/;
for (var i = 0; i < lines.length; i++) {
var t = lines[i].replace(/\r$/, "").trim();
if (t === "" || /^#{1,6}\s/.test(t) || /^```/.test(t) || t.charAt(0) === "|") continue;
var ln = t.replace(/^[-*+]?\s*/, "");
if (re.test(ln) && seen[ln] === undefined) {
seen[ln] = true;
out.push("- " + ln);
}
}
return out.join("\n");
}
function tocOf(text) {
text = text.replace(/\r\n?/g, "\n");
var lines = text.split("\n");
var toc = [];
for (var i = 0; i < lines.length; i++) {
var m = lines[i].match(/^(#{1,6})\s+(.*)$/);
if (m && m[2].trim() !== "") toc.push(m[2].trim());
}
return toc;
}
function tocMarkdown(text) {
var toc = tocOf(text);
if (toc.length === 0) return "";
return toc.map(function (t) { return "- " + t; }).join("\n");
}
function escapeHtml(s) {

@@ -283,2 +175,27 @@ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");

// 详情栏宽度适配(固化在本插件内,无需改动 harness):
// `ctx.layout` 只暴露 open/close/toggle,打开时默认 360px(harness 硬编码)。
// 插件包装 openDetails:面板动作由 AppFrame 挂载时经 attachPanels 送达,
// 拿到后在其基础上把详情栏设为 480px(在 harness clamp [300,520] 内,
// 窄视口让步链由 store 自动处理,用户拖拽值不受影响)。
// harness 接口若变化导致缺失,则静默回退为默认宽度。
var DDEC_DETAILS_WIDTH = 480;
var ddecWired = null;
if (typeof layout === "object" && layout !== null) {
var ddecAttach = typeof layout.attachPanels === "function" ? layout.attachPanels.bind(layout) : null;
var ddecOpen = typeof layout.openDetails === "function" ? layout.openDetails.bind(layout) : null;
if (ddecAttach !== null && ddecOpen !== null) {
layout.attachPanels = function (actions) {
ddecWired = actions;
ddecAttach(actions);
};
layout.openDetails = function () {
ddecOpen();
if (ddecWired !== null && typeof ddecWired.setDetails === "function") {
ddecWired.setDetails(DDEC_DETAILS_WIDTH);
}
};
}
}
var activeSeq = null;

@@ -299,5 +216,4 @@ var listeners = new Set();

function DecisionCard(props) {
var seq = props.seq;
var text = props.useSession(function (s) {
function textAt(useSession, seq) {
return useSession(function (s) {
if (s === undefined || s === null || !Array.isArray(s.nodes)) return "";

@@ -310,47 +226,26 @@ for (var i = 0; i < s.nodes.length; i++) {

});
var summary = summaryOf(text);
var st = React.useState("");
var draft = st[0];
var setDraft = st[1];
var hasSection = summary !== null && summary.trim() !== "";
var isFallback = false;
// 触发条件:要么有 agent 主动写的决策段(内容非空),要么内容语义上呈现多个决策点/多个方案。
if (!hasSection && !needsDecision(text)) return null;
if (!hasSection) {
summary = decisionSummary(text) || tocMarkdown(text);
isFallback = true;
}
}
function send() {
var body = draft.trim();
if (body === "") return;
if (props.inputActions) {
props.inputActions.setDraft("【决策】\n" + body);
props.inputActions.submit();
// 常驻入口:每条含文本的 agent 消息尾部显示「在侧栏查看全文」按钮。
// 首次点击打开右侧详情栏展示该条消息的完整 markdown;侧栏正在展示
// 该条消息时再次点击则收回。点击其它消息的按钮会切换展示目标。
function OpenSidebarButton(props) {
var text = textAt(props.useSession, props.seq);
if (text.trim() === "") return null;
var shown = useActiveSeq() === props.seq;
function toggle() {
if (activeSeq === props.seq) {
setActiveSeq(null);
if (layout) layout.closeDetails();
} else {
setActiveSeq(props.seq);
if (layout) layout.openDetails();
}
setDraft("");
}
function openSide() {
setActiveSeq(seq);
if (layout) layout.openDetails();
}
return React.createElement("div", { className: "ddec-card" },
React.createElement("div", { className: "ddec-card-head" },
React.createElement("span", { className: "ddec-card-title" }, isFallback ? "决策分屏" : "决策摘要"),
React.createElement("button", { type: "button", className: "ddec-btn ddec-btn-ghost", onClick: openSide }, "侧栏查看全文")
),
summary === ""
? React.createElement("div", { className: "ddec-md ddec-summary ddec-empty-text" }, isFallback ? "(完整方案见侧栏:点右上角「侧栏查看全文」)" : "(该段暂未填写具体决策点)")
: React.createElement("div", { className: "ddec-md ddec-summary", dangerouslySetInnerHTML: { __html: renderMarkdown(summary) } }),
React.createElement("textarea", {
className: "ddec-textarea",
placeholder: "在此写下你的决策,点“发送决策”发给 agent",
value: draft,
onChange: function (e) { setDraft(e.target.value); }
}),
React.createElement("div", { className: "ddec-foot" },
React.createElement("button", { type: "button", className: "ddec-btn ddec-btn-primary", onClick: send }, "发送决策")
)
);
return React.createElement("button", {
type: "button",
className: "ddec-open",
"aria-expanded": shown || undefined,
onClick: toggle
}, shown ? "收起侧栏" : "在侧栏查看全文");
}

@@ -368,12 +263,8 @@

});
var text = props.useSession(function (s) {
if (seq === null || s === undefined || s === null || !Array.isArray(s.nodes)) return "";
for (var i = 0; i < s.nodes.length; i++) {
var n = s.nodes[i];
if (n && n.kind === "assistant" && n.seq === seq) return textOf(n);
}
return "";
});
var text = textAt(props.useSession, seq);
var title = titleOf(text);
function close() { if (layout) layout.closeDetails(); }
function close() {
setActiveSeq(null);
if (layout) layout.closeDetails();
}

@@ -391,3 +282,3 @@ return React.createElement("div", { className: "ddec-side" },

seq === null || !found
? React.createElement("div", { className: "ddec-side-empty" }, "点击某条方案消息下方的「侧栏查看全文」,完整内容会显示在这里。")
? React.createElement("div", { className: "ddec-side-empty" }, "点击某条消息下方的「在侧栏查看全文」,完整内容会显示在这里。")
: React.createElement("div", { className: "ddec-md", dangerouslySetInnerHTML: { __html: renderMarkdown(text) } })

@@ -401,3 +292,3 @@ )

{ name: "conversation.chat.turnTail", priority: 1, select: function (owner) { return owner.seq; } },
DecisionCard
OpenSidebarButton
);

@@ -404,0 +295,0 @@ });

@@ -1,9 +0,4 @@

export const inject = ["systemPrompt"];
export function apply(ctx) {
ctx.systemPrompt.section({
name: "dsh-decision-split:decision-format",
order: 190,
text: '当你的回复需要用户就「多个决策点」或「多个可选方案」逐一作出选择时,请在回复末尾追加一个「## 决策摘要」段,用「**决策 N:**」(或「**方案 N:**」,N 从 1 开始)逐条列出每个待决定的点或方案。界面会自动据此弹出决策卡片,用户可在卡片里直接输入决策并回传给你。仅当确实存在需要用户拍板的多个决策点或多个备选方案时才添加;普通说明、无需用户决策的回复不要添加。',
});
}
// Host half deliberately empty after simplification: the sidebar-only plugin
// needs no system-prompt injection. Decisions flow through the harness's
// native ask_user_question tool instead of a markdown "## 决策摘要" section.
export function apply() {}
{
"name": "dsh-decision-split",
"version": "0.1.9",
"description": "决策分屏:agent 输出含「决策摘要」段的方案时,主窗口显示摘要与决策编辑框,右侧详情栏独立展示完整 markdown,决策一键发回 agent。",
"version": "0.2.2",
"description": "决策分屏:agent 消息尾部提供「在侧栏查看全文」入口,右侧详情栏独立滚动展示该条消息的完整 markdown,边看全文边回主窗口操作。",
"type": "module",

@@ -6,0 +6,0 @@ "main": "lib/index.js",

# dsh-decision-split
决策分屏插件 —— DeepSeek Harness Web UI 的决策工作台。
决策分屏插件 —— DeepSeek Harness Web UI 的分屏阅读工作台。
**解决什么痛点**:agent 输出长方案时,单一的滚动窗口让「通读全文」和「逐条拍板」互相干扰——看完后面忘了前面,对照多个决策点要反复上下滚动,决策只能靠脑子硬记。本插件把两者拆开:右侧详情栏独立滚动展示完整方案,主窗口固定显示决策摘要与决策输入框,边看全文边写决策、一键回传 agent。
**解决什么痛点**:agent 输出长方案时,单一的滚动窗口让「通读全文」和「边看边操作」互相干扰——看完后面忘了前面,对照多处细节要反复上下滚动。本插件在消息尾部提供一个「在侧栏查看全文」入口,把该条 agent 消息的完整 markdown 放到右侧详情栏独立滚动展示,主窗口保持当前操作位置不变。
当 agent 输出包含「决策摘要」段(任意标题层级,如 `## 决策摘要`、`### 决策要点`)的方案时:
- **入口**:每条含文本的 agent 消息尾部固定显示「在侧栏查看全文」按钮;
- **右侧详情栏**:独立滚动展示该条消息的完整 markdown(含表格/代码块/行内 code),侧栏标题自动取消息首个标题;打开时默认以 480px 宽展示(插件内置适配:在 harness 面板宽度 clamp [300,520] 内改默认,窄视口让步链与用户拖拽均不受影响,无需改 harness 任何文件);
- **无输入框、无决策回传**:决策与提问统一交给 harness 原生的 `ask_user_question` 能力,本插件不再内嵌输入框,避免与原生输入框、原生决策卡片冲突。
- **主窗口**:消息下方自动出现「决策摘要」卡片 —— 摘要内容 + 决策输入框 + 「侧栏查看全文」「发送决策」两个按钮;
- **右侧详情栏**:独立滚动展示该方案的完整 markdown,侧栏标题自动取方案首个标题;
- **发送决策**:决策以 `【决策】…` 前缀作为一条消息回传给 agent,让它基于你的决策继续推进。
视觉完全对齐 DSH Web UI 原生设计变量(`--dsw-*` tokens、MarkdownText 字体规范、原生按钮样式)。

@@ -19,3 +17,3 @@

*主窗口的「决策摘要」卡片(摘要 + 决策输入框 + 操作按钮),以及右侧详情栏的完整方案展示。*
*消息尾部的「在侧栏查看全文」入口,以及右侧详情栏的完整方案展示。*

@@ -33,3 +31,3 @@ ## 安装

```sh
dsh plugin --profile web add https://github.com/yuanbaoerer/dsh-decision-split/releases/latest/download/dsh-decision-split-0.1.6.tgz
dsh plugin --profile web add https://github.com/yuanbaoerer/dsh-decision-split/releases/latest/download/dsh-decision-split-0.2.2.tgz
```

@@ -41,16 +39,6 @@

装上即用,无需额外配置。触发遵循一个核心原则:**只有当回复确实呈现「多个决策点」或「多个可选方案」、需要你逐个阅读逐个拍板时才出卡**,而不是看标题存不存在、也不是看内容长短。
装上即用,无需额外配置。每条含文本的 agent 消息尾部都会出现「在侧栏查看全文」按钮,点击即在右侧详情栏独立滚动阅读该条消息的完整内容;侧栏正在展示该条消息时再点一次按钮(按钮此时显示为「收起侧栏」)即收回;点击其它消息的按钮可切换展示目标,点侧栏右上角 × 也可收起。
- **语义触发(客户端识别)**:回复内容满足以下任一条件即触发(纯说明性长文、普通问答不会误触发):
- **≥2 个决策点**:如 `**决策 1:**`、`**决策 2:**`、`决策点二:`、`待确认:` 等;
- **≥2 个方案/选项**:如 `方案 A`、`方案 一`、`选项 1`、`备选 2` 等;
- **对比类表格**:表头含 `方案/选项/备选/对比/选择` 且有 ≥2 行对比数据。
- **决策段触发**:回复中出现 agent 主动写的非空决策段标题(任意层级 `#`–`######`、忽略大小写、允许结尾冒号)也触发,摘要区展示该段内容:
> 决策/提问请使用对话里的原生提问能力(agent 调用 `ask_user_question` 时界面会弹出可点选卡片),本插件专注「分屏阅读」这一件事。
`决策摘要` · `决策要点` · `待决策清单` · `决策清单` · `决策事项` · `方案对比` · `方案选择` · `需要决策` · `待决策` · `待确认` · `待拍板` · `请你决定` · `请拍板` · `决策项` · `决策点` · `Decision Summary` · `Decision Points` · `Decision Required` · `Decision Needed` · `Please Decide` · `Please Choose` · `Need Your Decision` · `Options` · `Choices`
- **host 注入提示词**:插件会在系统提示中告知 agent——当回复包含需要你拍板的决策点、或给出多个方案让你选择时,用 `**决策 N:**` 逐条列出,以便即便 agent 不写标题、客户端也能按上面的语义规则识别出来。
> 也可在对话里给 agent 补一句「输出方案时若需我拍板,请在最后用 `## 决策摘要` 段列出决策点」——手动约定与自动注入效果相同,属可选的加强。
## License

@@ -57,0 +45,0 @@