@fieldwangai/agentflow 0.1.40 → 0.1.42
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/bin/lib/ui-server.mjs +270 -35
- package/builtin/web-ui/dist/assets/index-CuOti87V.css +1 -0
- package/builtin/web-ui/dist/assets/{index-COHXnLDo.js → index-D9T2uM0l.js} +49 -49
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-workspace-chart/SKILL.md +102 -0
- package/skills/agentflow-workspace-graph/SKILL.md +4 -0
- package/skills/agentflow-workspace-html/SKILL.md +65 -0
- package/skills/agentflow-workspace-image/SKILL.md +52 -0
- package/skills/agentflow-workspace-table/SKILL.md +65 -0
- package/builtin/web-ui/dist/assets/index-YXZNOj6z.css +0 -1
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -136,11 +136,35 @@ const BUILTIN_SKILL_COLLECTIONS = [
|
|
|
136
136
|
"agentflow-workspace-markdown",
|
|
137
137
|
"agentflow-workspace-mermaid",
|
|
138
138
|
"agentflow-workspace-ascii",
|
|
139
|
+
"agentflow-workspace-chart",
|
|
140
|
+
"agentflow-workspace-table",
|
|
141
|
+
"agentflow-workspace-html",
|
|
142
|
+
"agentflow-workspace-image",
|
|
139
143
|
"agentflow-node-reference",
|
|
140
144
|
"agentflow-placeholder-reference",
|
|
141
145
|
"agentflow-runtime-reference",
|
|
142
146
|
],
|
|
143
147
|
legacyDefaultKeys: [
|
|
148
|
+
[
|
|
149
|
+
"agentflow-workspace-graph",
|
|
150
|
+
"agentflow-workspace-markdown",
|
|
151
|
+
"agentflow-workspace-mermaid",
|
|
152
|
+
"agentflow-workspace-ascii",
|
|
153
|
+
"agentflow-workspace-chart",
|
|
154
|
+
"agentflow-workspace-table",
|
|
155
|
+
"agentflow-node-reference",
|
|
156
|
+
"agentflow-placeholder-reference",
|
|
157
|
+
"agentflow-runtime-reference",
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
"agentflow-workspace-graph",
|
|
161
|
+
"agentflow-workspace-markdown",
|
|
162
|
+
"agentflow-workspace-mermaid",
|
|
163
|
+
"agentflow-workspace-ascii",
|
|
164
|
+
"agentflow-node-reference",
|
|
165
|
+
"agentflow-placeholder-reference",
|
|
166
|
+
"agentflow-runtime-reference",
|
|
167
|
+
],
|
|
144
168
|
[
|
|
145
169
|
"agentflow-flow-add-instances",
|
|
146
170
|
"agentflow-flow-edit-node-fields",
|
|
@@ -1410,10 +1434,171 @@ function workspaceStringifyOutputValue(value) {
|
|
|
1410
1434
|
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
1411
1435
|
}
|
|
1412
1436
|
|
|
1437
|
+
function workspaceUnescapeLooseJsonString(value) {
|
|
1438
|
+
return String(value ?? "")
|
|
1439
|
+
.replace(/\\n/g, "\n")
|
|
1440
|
+
.replace(/\\r/g, "\r")
|
|
1441
|
+
.replace(/\\t/g, "\t")
|
|
1442
|
+
.replace(/\\"/g, '"')
|
|
1443
|
+
.replace(/\\\\/g, "\\")
|
|
1444
|
+
.trim();
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function workspaceFindMatchingDelimiter(text, openIndex, openChar = "{", closeChar = "}") {
|
|
1448
|
+
const raw = String(text || "");
|
|
1449
|
+
if (raw[openIndex] !== openChar) return -1;
|
|
1450
|
+
let depth = 0;
|
|
1451
|
+
let quote = "";
|
|
1452
|
+
let escaped = false;
|
|
1453
|
+
for (let i = openIndex; i < raw.length; i += 1) {
|
|
1454
|
+
const ch = raw[i];
|
|
1455
|
+
if (quote) {
|
|
1456
|
+
if (escaped) {
|
|
1457
|
+
escaped = false;
|
|
1458
|
+
} else if (ch === "\\") {
|
|
1459
|
+
escaped = true;
|
|
1460
|
+
} else if (ch === quote) {
|
|
1461
|
+
quote = "";
|
|
1462
|
+
}
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
if (ch === '"' || ch === "'") {
|
|
1466
|
+
quote = ch;
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
if (ch === openChar) depth += 1;
|
|
1470
|
+
if (ch === closeChar) {
|
|
1471
|
+
depth -= 1;
|
|
1472
|
+
if (depth === 0) return i;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return -1;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function workspaceParseLooseJsonValue(text, startIndex, limitIndex = String(text || "").length) {
|
|
1479
|
+
const raw = String(text || "");
|
|
1480
|
+
let i = startIndex;
|
|
1481
|
+
while (i < limitIndex && /\s/.test(raw[i])) i += 1;
|
|
1482
|
+
if (i >= limitIndex) return { value: "", end: i };
|
|
1483
|
+
const ch = raw[i];
|
|
1484
|
+
if (ch === "{" || ch === "[") {
|
|
1485
|
+
const close = workspaceFindMatchingDelimiter(raw, i, ch, ch === "{" ? "}" : "]");
|
|
1486
|
+
const end = close >= 0 ? close + 1 : limitIndex;
|
|
1487
|
+
const slice = raw.slice(i, end).trim();
|
|
1488
|
+
try {
|
|
1489
|
+
return { value: workspaceStringifyOutputValue(JSON.parse(slice)), end };
|
|
1490
|
+
} catch {
|
|
1491
|
+
return { value: slice, end };
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (ch === '"' || ch === "'") {
|
|
1495
|
+
const quote = ch;
|
|
1496
|
+
let escaped = false;
|
|
1497
|
+
let end = i + 1;
|
|
1498
|
+
for (; end < limitIndex; end += 1) {
|
|
1499
|
+
const c = raw[end];
|
|
1500
|
+
if (escaped) {
|
|
1501
|
+
escaped = false;
|
|
1502
|
+
} else if (c === "\\") {
|
|
1503
|
+
escaped = true;
|
|
1504
|
+
} else if (c === quote) {
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
const body = raw.slice(i + 1, end < limitIndex ? end : limitIndex);
|
|
1509
|
+
return { value: workspaceUnescapeLooseJsonString(body), end: Math.min(end + 1, limitIndex) };
|
|
1510
|
+
}
|
|
1511
|
+
let end = i;
|
|
1512
|
+
while (end < limitIndex && raw[end] !== "," && raw[end] !== "\n" && raw[end] !== "\r" && raw[end] !== "}") end += 1;
|
|
1513
|
+
const slice = raw.slice(i, end).trim().replace(/^["'`]|["'`]$/g, "");
|
|
1514
|
+
return { value: workspaceUnescapeLooseJsonString(slice), end };
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function workspaceExtractLooseOutParams(raw) {
|
|
1518
|
+
const text = String(raw || "");
|
|
1519
|
+
const out = {};
|
|
1520
|
+
const startMatch = /["']outParams["']\s*:\s*\{/i.exec(text);
|
|
1521
|
+
if (!startMatch) return out;
|
|
1522
|
+
const openIndex = text.indexOf("{", startMatch.index);
|
|
1523
|
+
const closeIndex = workspaceFindMatchingDelimiter(text, openIndex);
|
|
1524
|
+
const endLimit = closeIndex >= 0 ? closeIndex : text.length;
|
|
1525
|
+
const block = text.slice(openIndex, closeIndex >= 0 ? closeIndex + 1 : text.length);
|
|
1526
|
+
try {
|
|
1527
|
+
const parsed = JSON.parse(block);
|
|
1528
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1529
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1530
|
+
const name = String(key || "").trim();
|
|
1531
|
+
if (name) out[name] = workspaceStringifyOutputValue(value);
|
|
1532
|
+
}
|
|
1533
|
+
return out;
|
|
1534
|
+
}
|
|
1535
|
+
} catch {
|
|
1536
|
+
/* fall through to loose top-level scanning */
|
|
1537
|
+
}
|
|
1538
|
+
let i = openIndex + 1;
|
|
1539
|
+
while (i < endLimit) {
|
|
1540
|
+
while (i < endLimit && /[\s,]/.test(text[i])) i += 1;
|
|
1541
|
+
if (i >= endLimit) break;
|
|
1542
|
+
let key = "";
|
|
1543
|
+
if (text[i] === '"' || text[i] === "'") {
|
|
1544
|
+
const quote = text[i];
|
|
1545
|
+
const keyStart = i + 1;
|
|
1546
|
+
i = keyStart;
|
|
1547
|
+
while (i < endLimit && text[i] !== quote) i += 1;
|
|
1548
|
+
key = text.slice(keyStart, i).trim();
|
|
1549
|
+
i += 1;
|
|
1550
|
+
} else {
|
|
1551
|
+
const keyStart = i;
|
|
1552
|
+
while (i < endLimit && /[A-Za-z0-9_-]/.test(text[i])) i += 1;
|
|
1553
|
+
key = text.slice(keyStart, i).trim();
|
|
1554
|
+
}
|
|
1555
|
+
while (i < endLimit && /\s/.test(text[i])) i += 1;
|
|
1556
|
+
if (text[i] !== ":") {
|
|
1557
|
+
i += 1;
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
i += 1;
|
|
1561
|
+
const parsedValue = workspaceParseLooseJsonValue(text, i, endLimit);
|
|
1562
|
+
if (key) out[key] = String(parsedValue.value ?? "").trim();
|
|
1563
|
+
i = parsedValue.end;
|
|
1564
|
+
}
|
|
1565
|
+
return out;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function workspaceExtractLooseResult(raw) {
|
|
1569
|
+
const text = String(raw || "").trim();
|
|
1570
|
+
const resultMatch = /["']result["']\s*:\s*(["'])/i.exec(text);
|
|
1571
|
+
if (!resultMatch) return "";
|
|
1572
|
+
const quote = resultMatch[1];
|
|
1573
|
+
const start = resultMatch.index + resultMatch[0].length;
|
|
1574
|
+
const outParamsMatch = /,\s*["']outParams["']\s*:/i.exec(text.slice(start));
|
|
1575
|
+
if (outParamsMatch) {
|
|
1576
|
+
const end = start + outParamsMatch.index;
|
|
1577
|
+
let value = text.slice(start, end).trim();
|
|
1578
|
+
if (value.endsWith(quote)) value = value.slice(0, -1);
|
|
1579
|
+
return workspaceUnescapeLooseJsonString(value);
|
|
1580
|
+
}
|
|
1581
|
+
const end = text.lastIndexOf(quote);
|
|
1582
|
+
if (end > start) return workspaceUnescapeLooseJsonString(text.slice(start, end));
|
|
1583
|
+
return "";
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1413
1586
|
function workspaceStructuredAgentOutput(content) {
|
|
1414
1587
|
const raw = String(content || "").trim();
|
|
1415
1588
|
const parsed = workspaceParseJsonObjectFromText(raw);
|
|
1416
|
-
if (!parsed)
|
|
1589
|
+
if (!parsed) {
|
|
1590
|
+
const looseResult = workspaceExtractLooseResult(raw);
|
|
1591
|
+
const looseOutParams = workspaceExtractLooseOutParams(raw);
|
|
1592
|
+
if (looseResult || Object.keys(looseOutParams).length) {
|
|
1593
|
+
return {
|
|
1594
|
+
result: looseResult || raw,
|
|
1595
|
+
outParams: looseOutParams,
|
|
1596
|
+
structured: true,
|
|
1597
|
+
parsed: null,
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
return { result: raw, outParams: {}, structured: false, parsed: null };
|
|
1601
|
+
}
|
|
1417
1602
|
const hasEnvelope = Object.prototype.hasOwnProperty.call(parsed, "result") ||
|
|
1418
1603
|
Object.prototype.hasOwnProperty.call(parsed, "outParams");
|
|
1419
1604
|
if (!hasEnvelope) return { result: raw, outParams: {}, structured: false, parsed };
|
|
@@ -1446,7 +1631,12 @@ function workspaceExtractNamedOutputValue(content, slotName) {
|
|
|
1446
1631
|
return workspaceStringifyOutputValue(value);
|
|
1447
1632
|
}
|
|
1448
1633
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1634
|
+
const looseOutParams = workspaceExtractLooseOutParams(content);
|
|
1635
|
+
if (Object.prototype.hasOwnProperty.call(looseOutParams, name)) {
|
|
1636
|
+
return String(looseOutParams[name] ?? "").trim();
|
|
1637
|
+
}
|
|
1449
1638
|
const patterns = [
|
|
1639
|
+
new RegExp(`["']?${escaped}["']?\\s*:\\s*["']?([^"',}\\n\\r]+)`, "i"),
|
|
1450
1640
|
new RegExp(`(?:\\$\\{${escaped}\\}|\\$${escaped})\\s*[=::]\\s*([^\\n\\r]+)`, "i"),
|
|
1451
1641
|
new RegExp(`(?:^|[\\n\\r])\\s*${escaped}\\s*[=::]\\s*([^\\n\\r]+)`, "i"),
|
|
1452
1642
|
];
|
|
@@ -1522,6 +1712,60 @@ function workspaceDisplayKind(definitionId) {
|
|
|
1522
1712
|
return "";
|
|
1523
1713
|
}
|
|
1524
1714
|
|
|
1715
|
+
function workspaceOutputFieldForSlot(slot, index = 0) {
|
|
1716
|
+
const name = String(slot?.name || "").trim();
|
|
1717
|
+
if (!name || name === "result" || name === "content" || index === 0) return "result";
|
|
1718
|
+
return `outParams.${name}`;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
function workspaceDisplayKindExample(kind, field) {
|
|
1722
|
+
if (kind === "table") return { columns: ["列名1", "列名2"], rows: [["值1", "值2"]] };
|
|
1723
|
+
if (kind === "chart") return { type: "chart", version: "1.0", renderer: "echarts", option: { xAxis: { type: "category", data: [] }, yAxis: { type: "value" }, series: [{ type: "bar", data: [] }] } };
|
|
1724
|
+
if (kind === "html") return "<可直接渲染的 HTML>";
|
|
1725
|
+
if (kind === "mermaid") return "flowchart TD\n A[开始] --> B[结束]";
|
|
1726
|
+
if (kind === "ascii") return "+---+\n| |\n+---+";
|
|
1727
|
+
if (kind === "image") return "<图片 URL 或 data URL>";
|
|
1728
|
+
if (kind === "markdown") return field === "result" ? "<给用户看的完整 Markdown 正文>" : "<Markdown 正文>";
|
|
1729
|
+
return `<${field} 的值>`;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function workspaceDisplayFieldRule(kind, field, slotName = "") {
|
|
1733
|
+
const slotText = field === "result" ? "`result` 字段" : `\`${field}\``;
|
|
1734
|
+
const prefix = slotName ? `- 输出引脚 \`${slotName}\` 连接了 ${kind} 展示节点:` : `- 下游连接了 ${kind} 展示节点:`;
|
|
1735
|
+
if (kind === "html") return `${prefix}将可直接放入 iframe 渲染的 HTML 放在 ${slotText} 中。可以是完整 HTML 文档或 HTML fragment;不要使用 Markdown 代码围栏。`;
|
|
1736
|
+
if (kind === "markdown") return `${prefix}将 Markdown 正文放在 ${slotText} 中;除非正文确实需要代码块,否则不要额外包裹代码围栏。`;
|
|
1737
|
+
if (kind === "mermaid") return `${prefix}将 Mermaid 图表代码放在 ${slotText} 中,例如 flowchart/sequenceDiagram;不要使用 Markdown 代码围栏。`;
|
|
1738
|
+
if (kind === "ascii") return `${prefix}将纯文本/ASCII 图或表格放在 ${slotText} 中;不要输出 HTML 或 Markdown 装饰。`;
|
|
1739
|
+
if (kind === "image") return `${prefix}将可作为 img src 使用的图片地址、data URL 或 base64 data URL 放在 ${slotText} 中;不要输出 Markdown 图片语法。`;
|
|
1740
|
+
if (kind === "chart") return `${prefix}将 ChartSpec JSON 对象放在 ${slotText} 中。ChartSpec 必须包含 "type":"chart"、"version":"1.0"、"renderer":"echarts"、"option";option.series[].type 只使用 line/bar/pie/scatter/radar/heatmap/tree/treemap/sunburst/sankey/graph/gauge/funnel;不要输出 HTML、script、iframe 或 JS 函数。`;
|
|
1741
|
+
if (kind === "table") return `${prefix}将表格数据放在 ${slotText} 中。推荐格式:{"columns":["列名1","列名2"],"rows":[["值1","值2"]]};也可使用对象数组、Markdown 表格、CSV 或 TSV。不要输出 HTML。`;
|
|
1742
|
+
return `${prefix}将展示内容放在 ${slotText} 中。`;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function workspaceDownstreamOutputDisplayBindings(graph, nodeId) {
|
|
1746
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1747
|
+
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
1748
|
+
const source = instances[String(nodeId || "")] || {};
|
|
1749
|
+
const output = Array.isArray(source.output) ? source.output : [];
|
|
1750
|
+
const bindings = [];
|
|
1751
|
+
for (const edge of edges) {
|
|
1752
|
+
if (String(edge?.source || "") !== String(nodeId)) continue;
|
|
1753
|
+
const target = instances[String(edge?.target || "")];
|
|
1754
|
+
const kind = workspaceDisplayKind(target?.definitionId);
|
|
1755
|
+
if (!kind) continue;
|
|
1756
|
+
const index = workspaceHandleIndex(edge?.sourceHandle, "output");
|
|
1757
|
+
const slot = output[index] || null;
|
|
1758
|
+
const name = String(slot?.name || "").trim() || (index === 0 ? "result" : `output-${index}`);
|
|
1759
|
+
bindings.push({
|
|
1760
|
+
kind,
|
|
1761
|
+
index,
|
|
1762
|
+
name,
|
|
1763
|
+
field: workspaceOutputFieldForSlot(slot, index),
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
return bindings;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1525
1769
|
function normalizeHtmlDisplayContent(content) {
|
|
1526
1770
|
let text = String(content || "").trim();
|
|
1527
1771
|
if (!text) return "";
|
|
@@ -1556,50 +1800,35 @@ function normalizeHtmlDisplayContent(content) {
|
|
|
1556
1800
|
}
|
|
1557
1801
|
|
|
1558
1802
|
function workspaceDownstreamDisplayRequirements(graph, nodeId) {
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
const
|
|
1562
|
-
for (const edge of edges) {
|
|
1563
|
-
if (String(edge?.source || "") !== String(nodeId)) continue;
|
|
1564
|
-
const target = instances[String(edge?.target || "")];
|
|
1565
|
-
const kind = workspaceDisplayKind(target?.definitionId);
|
|
1566
|
-
if (kind) kinds.add(kind);
|
|
1567
|
-
}
|
|
1568
|
-
if (kinds.size === 0) return "";
|
|
1803
|
+
const bindings = workspaceDownstreamOutputDisplayBindings(graph, nodeId);
|
|
1804
|
+
if (bindings.length === 0) return "";
|
|
1805
|
+
const seen = new Set();
|
|
1569
1806
|
const rules = [];
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
rules.push(
|
|
1575
|
-
}
|
|
1576
|
-
if (kinds.has("mermaid")) {
|
|
1577
|
-
rules.push("- 下游连接了 Mermaid 展示节点:将 Mermaid 图表代码放在输出协议的 `result` 字段中,例如 flowchart/sequenceDiagram;不要使用 Markdown 代码围栏。");
|
|
1578
|
-
}
|
|
1579
|
-
if (kinds.has("ascii")) {
|
|
1580
|
-
rules.push("- 下游连接了 ASCII 展示节点:将纯文本/ASCII 图或表格放在输出协议的 `result` 字段中;不要输出 HTML 或 Markdown 装饰。");
|
|
1581
|
-
}
|
|
1582
|
-
if (kinds.has("image")) {
|
|
1583
|
-
rules.push("- 下游连接了图片展示节点:将可作为 img src 使用的图片地址、data URL 或 base64 data URL 放在输出协议的 `result` 字段中;不要输出 Markdown 图片语法。");
|
|
1584
|
-
}
|
|
1585
|
-
if (kinds.has("chart")) {
|
|
1586
|
-
rules.push('- 下游连接了 Chart 展示节点:将 ChartSpec JSON 对象放在输出协议的 `result` 字段中。ChartSpec 必须包含 `"type":"chart"`、`"version":"1.0"`、`"renderer":"echarts"`、`"option"`;`option.series[].type` 只使用 line/bar/pie/scatter/radar/heatmap/tree/treemap/sunburst/sankey/graph/gauge/funnel;不要输出 HTML、script、iframe 或 JS 函数。');
|
|
1587
|
-
}
|
|
1588
|
-
if (kinds.has("table")) {
|
|
1589
|
-
rules.push('- 下游连接了表格展示节点:将表格数据放在输出协议的 `result` 字段中。推荐格式:`{"columns":["列名1","列名2"],"rows":[["值1","值2"]]}`;也可使用对象数组、Markdown 表格、CSV 或 TSV。不要输出 HTML。');
|
|
1807
|
+
for (const binding of bindings) {
|
|
1808
|
+
const key = `${binding.field}:${binding.kind}`;
|
|
1809
|
+
if (seen.has(key)) continue;
|
|
1810
|
+
seen.add(key);
|
|
1811
|
+
rules.push(workspaceDisplayFieldRule(binding.kind, binding.field, binding.name));
|
|
1590
1812
|
}
|
|
1591
1813
|
return [
|
|
1592
1814
|
"## 下游输出要求",
|
|
1593
1815
|
"",
|
|
1594
1816
|
...rules,
|
|
1595
1817
|
"",
|
|
1818
|
+
"这些要求按输出引脚分别生效:不要把非 `result` 引脚连接的展示内容误写到 `result`;具名引脚应写入 `outParams.<引脚名>`。",
|
|
1596
1819
|
"如果用户任务与下游展示格式没有冲突,优先满足上述格式要求;如果用户明确指定了其他格式,以用户任务为准。",
|
|
1597
1820
|
].join("\n");
|
|
1598
1821
|
}
|
|
1599
1822
|
|
|
1600
1823
|
function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
1601
1824
|
const instance = graph?.instances?.[nodeId] || {};
|
|
1602
|
-
const
|
|
1825
|
+
const outputSlots = Array.isArray(instance.output) ? instance.output : [];
|
|
1826
|
+
const displayBindings = workspaceDownstreamOutputDisplayBindings(graph, nodeId);
|
|
1827
|
+
const displayByField = new Map();
|
|
1828
|
+
for (const binding of displayBindings) {
|
|
1829
|
+
if (!displayByField.has(binding.field)) displayByField.set(binding.field, binding.kind);
|
|
1830
|
+
}
|
|
1831
|
+
const slots = outputSlots
|
|
1603
1832
|
.filter((slot) => {
|
|
1604
1833
|
const name = String(slot?.name || "").trim();
|
|
1605
1834
|
const type = String(slot?.type || "");
|
|
@@ -1607,18 +1836,24 @@ function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
|
1607
1836
|
})
|
|
1608
1837
|
.map((slot) => String(slot.name).trim());
|
|
1609
1838
|
const outParamsExample = slots.length
|
|
1610
|
-
? Object.fromEntries(slots.map((name) => [name,
|
|
1839
|
+
? Object.fromEntries(slots.map((name) => [name, workspaceDisplayKindExample(displayByField.get(`outParams.${name}`) || "", name)]))
|
|
1611
1840
|
: {};
|
|
1841
|
+
const resultExample = workspaceDisplayKindExample(displayByField.get("result") || "markdown", "result");
|
|
1842
|
+
const slotDisplayRules = displayBindings
|
|
1843
|
+
.map((binding) => `- 输出引脚 \`${binding.name}\` -> ${binding.kind} 展示节点:写入 \`${binding.field}\`。`)
|
|
1844
|
+
.filter((line, index, arr) => arr.indexOf(line) === index);
|
|
1612
1845
|
return [
|
|
1613
1846
|
"## Workspace 输出协议",
|
|
1614
1847
|
"",
|
|
1615
|
-
"最终回复必须是一个 JSON
|
|
1848
|
+
"最终回复必须是一个 JSON 对象,必须直接以 `{` 开头并以 `}` 结尾;不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字、进度说明或自然语言前后缀。",
|
|
1849
|
+
"JSON 必须可被 `JSON.parse` 解析;如果 `result` 是多行 Markdown,必须在 JSON 字符串里使用 `\\n` 转义换行,不能把裸 Markdown 直接塞进未转义的字符串。",
|
|
1616
1850
|
"固定格式:",
|
|
1617
1851
|
"",
|
|
1618
|
-
JSON.stringify({ result:
|
|
1852
|
+
JSON.stringify({ result: resultExample, outParams: outParamsExample }, null, 2),
|
|
1619
1853
|
"",
|
|
1620
1854
|
"- `result`:完整正文,写入 `result` / `content` 输出口,直连默认展示节点时展示它。",
|
|
1621
1855
|
"- `outParams`:具名输出参数,只写入同名输出引脚。",
|
|
1856
|
+
...(slotDisplayRules.length ? ["- 当前输出引脚与展示节点映射:", ...slotDisplayRules] : []),
|
|
1622
1857
|
...(slots.length
|
|
1623
1858
|
? [`- 当前节点具名输出槽:${slots.map((name) => `\`${name}\``).join("、")}。例如任务要求写入 \`${slots[0]}\` 时,放到 \`outParams.${slots[0]}\`。`]
|
|
1624
1859
|
: ["- 当前节点没有额外具名输出槽,`outParams` 返回空对象即可。"]),
|