@fieldwangai/agentflow 0.1.40 → 0.1.41
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 +143 -35
- package/builtin/web-ui/dist/assets/index-DyBxNnlo.css +1 -0
- package/builtin/web-ui/dist/assets/{index-COHXnLDo.js → index-TXmzGUhf.js} +47 -47
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-YXZNOj6z.css +0 -1
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -1410,10 +1410,68 @@ function workspaceStringifyOutputValue(value) {
|
|
|
1410
1410
|
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
1411
1411
|
}
|
|
1412
1412
|
|
|
1413
|
+
function workspaceUnescapeLooseJsonString(value) {
|
|
1414
|
+
return String(value ?? "")
|
|
1415
|
+
.replace(/\\n/g, "\n")
|
|
1416
|
+
.replace(/\\r/g, "\r")
|
|
1417
|
+
.replace(/\\t/g, "\t")
|
|
1418
|
+
.replace(/\\"/g, '"')
|
|
1419
|
+
.replace(/\\\\/g, "\\")
|
|
1420
|
+
.trim();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function workspaceExtractLooseOutParams(raw) {
|
|
1424
|
+
const text = String(raw || "");
|
|
1425
|
+
const out = {};
|
|
1426
|
+
const startMatch = /["']outParams["']\s*:\s*\{/i.exec(text);
|
|
1427
|
+
if (!startMatch) return out;
|
|
1428
|
+
const start = startMatch.index + startMatch[0].length;
|
|
1429
|
+
const end = text.indexOf("}", start);
|
|
1430
|
+
const block = end >= start ? text.slice(start, end) : text.slice(start);
|
|
1431
|
+
const pairRe = /["']?([A-Za-z_][A-Za-z0-9_-]*)["']?\s*:\s*(?:"([^"]*)"|'([^']*)'|([^,\n\r}]+))/g;
|
|
1432
|
+
let match;
|
|
1433
|
+
while ((match = pairRe.exec(block))) {
|
|
1434
|
+
const key = String(match[1] || "").trim();
|
|
1435
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
1436
|
+
if (key) out[key] = workspaceUnescapeLooseJsonString(value).replace(/^["'`]|["'`]$/g, "").trim();
|
|
1437
|
+
}
|
|
1438
|
+
return out;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function workspaceExtractLooseResult(raw) {
|
|
1442
|
+
const text = String(raw || "").trim();
|
|
1443
|
+
const resultMatch = /["']result["']\s*:\s*(["'])/i.exec(text);
|
|
1444
|
+
if (!resultMatch) return "";
|
|
1445
|
+
const quote = resultMatch[1];
|
|
1446
|
+
const start = resultMatch.index + resultMatch[0].length;
|
|
1447
|
+
const outParamsMatch = /,\s*["']outParams["']\s*:/i.exec(text.slice(start));
|
|
1448
|
+
if (outParamsMatch) {
|
|
1449
|
+
const end = start + outParamsMatch.index;
|
|
1450
|
+
let value = text.slice(start, end).trim();
|
|
1451
|
+
if (value.endsWith(quote)) value = value.slice(0, -1);
|
|
1452
|
+
return workspaceUnescapeLooseJsonString(value);
|
|
1453
|
+
}
|
|
1454
|
+
const end = text.lastIndexOf(quote);
|
|
1455
|
+
if (end > start) return workspaceUnescapeLooseJsonString(text.slice(start, end));
|
|
1456
|
+
return "";
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1413
1459
|
function workspaceStructuredAgentOutput(content) {
|
|
1414
1460
|
const raw = String(content || "").trim();
|
|
1415
1461
|
const parsed = workspaceParseJsonObjectFromText(raw);
|
|
1416
|
-
if (!parsed)
|
|
1462
|
+
if (!parsed) {
|
|
1463
|
+
const looseResult = workspaceExtractLooseResult(raw);
|
|
1464
|
+
const looseOutParams = workspaceExtractLooseOutParams(raw);
|
|
1465
|
+
if (looseResult || Object.keys(looseOutParams).length) {
|
|
1466
|
+
return {
|
|
1467
|
+
result: looseResult || raw,
|
|
1468
|
+
outParams: looseOutParams,
|
|
1469
|
+
structured: true,
|
|
1470
|
+
parsed: null,
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
return { result: raw, outParams: {}, structured: false, parsed: null };
|
|
1474
|
+
}
|
|
1417
1475
|
const hasEnvelope = Object.prototype.hasOwnProperty.call(parsed, "result") ||
|
|
1418
1476
|
Object.prototype.hasOwnProperty.call(parsed, "outParams");
|
|
1419
1477
|
if (!hasEnvelope) return { result: raw, outParams: {}, structured: false, parsed };
|
|
@@ -1446,7 +1504,12 @@ function workspaceExtractNamedOutputValue(content, slotName) {
|
|
|
1446
1504
|
return workspaceStringifyOutputValue(value);
|
|
1447
1505
|
}
|
|
1448
1506
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1507
|
+
const looseOutParams = workspaceExtractLooseOutParams(content);
|
|
1508
|
+
if (Object.prototype.hasOwnProperty.call(looseOutParams, name)) {
|
|
1509
|
+
return String(looseOutParams[name] ?? "").trim();
|
|
1510
|
+
}
|
|
1449
1511
|
const patterns = [
|
|
1512
|
+
new RegExp(`["']?${escaped}["']?\\s*:\\s*["']?([^"',}\\n\\r]+)`, "i"),
|
|
1450
1513
|
new RegExp(`(?:\\$\\{${escaped}\\}|\\$${escaped})\\s*[=::]\\s*([^\\n\\r]+)`, "i"),
|
|
1451
1514
|
new RegExp(`(?:^|[\\n\\r])\\s*${escaped}\\s*[=::]\\s*([^\\n\\r]+)`, "i"),
|
|
1452
1515
|
];
|
|
@@ -1522,6 +1585,60 @@ function workspaceDisplayKind(definitionId) {
|
|
|
1522
1585
|
return "";
|
|
1523
1586
|
}
|
|
1524
1587
|
|
|
1588
|
+
function workspaceOutputFieldForSlot(slot, index = 0) {
|
|
1589
|
+
const name = String(slot?.name || "").trim();
|
|
1590
|
+
if (!name || name === "result" || name === "content" || index === 0) return "result";
|
|
1591
|
+
return `outParams.${name}`;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function workspaceDisplayKindExample(kind, field) {
|
|
1595
|
+
if (kind === "table") return { columns: ["列名1", "列名2"], rows: [["值1", "值2"]] };
|
|
1596
|
+
if (kind === "chart") return { type: "chart", version: "1.0", renderer: "echarts", option: { xAxis: { type: "category", data: [] }, yAxis: { type: "value" }, series: [{ type: "bar", data: [] }] } };
|
|
1597
|
+
if (kind === "html") return "<可直接渲染的 HTML>";
|
|
1598
|
+
if (kind === "mermaid") return "flowchart TD\n A[开始] --> B[结束]";
|
|
1599
|
+
if (kind === "ascii") return "+---+\n| |\n+---+";
|
|
1600
|
+
if (kind === "image") return "<图片 URL 或 data URL>";
|
|
1601
|
+
if (kind === "markdown") return field === "result" ? "<给用户看的完整 Markdown 正文>" : "<Markdown 正文>";
|
|
1602
|
+
return `<${field} 的值>`;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function workspaceDisplayFieldRule(kind, field, slotName = "") {
|
|
1606
|
+
const slotText = field === "result" ? "`result` 字段" : `\`${field}\``;
|
|
1607
|
+
const prefix = slotName ? `- 输出引脚 \`${slotName}\` 连接了 ${kind} 展示节点:` : `- 下游连接了 ${kind} 展示节点:`;
|
|
1608
|
+
if (kind === "html") return `${prefix}将可直接放入 iframe 渲染的 HTML 放在 ${slotText} 中。可以是完整 HTML 文档或 HTML fragment;不要使用 Markdown 代码围栏。`;
|
|
1609
|
+
if (kind === "markdown") return `${prefix}将 Markdown 正文放在 ${slotText} 中;除非正文确实需要代码块,否则不要额外包裹代码围栏。`;
|
|
1610
|
+
if (kind === "mermaid") return `${prefix}将 Mermaid 图表代码放在 ${slotText} 中,例如 flowchart/sequenceDiagram;不要使用 Markdown 代码围栏。`;
|
|
1611
|
+
if (kind === "ascii") return `${prefix}将纯文本/ASCII 图或表格放在 ${slotText} 中;不要输出 HTML 或 Markdown 装饰。`;
|
|
1612
|
+
if (kind === "image") return `${prefix}将可作为 img src 使用的图片地址、data URL 或 base64 data URL 放在 ${slotText} 中;不要输出 Markdown 图片语法。`;
|
|
1613
|
+
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 函数。`;
|
|
1614
|
+
if (kind === "table") return `${prefix}将表格数据放在 ${slotText} 中。推荐格式:{"columns":["列名1","列名2"],"rows":[["值1","值2"]]};也可使用对象数组、Markdown 表格、CSV 或 TSV。不要输出 HTML。`;
|
|
1615
|
+
return `${prefix}将展示内容放在 ${slotText} 中。`;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function workspaceDownstreamOutputDisplayBindings(graph, nodeId) {
|
|
1619
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1620
|
+
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
1621
|
+
const source = instances[String(nodeId || "")] || {};
|
|
1622
|
+
const output = Array.isArray(source.output) ? source.output : [];
|
|
1623
|
+
const bindings = [];
|
|
1624
|
+
for (const edge of edges) {
|
|
1625
|
+
if (String(edge?.source || "") !== String(nodeId)) continue;
|
|
1626
|
+
const target = instances[String(edge?.target || "")];
|
|
1627
|
+
const kind = workspaceDisplayKind(target?.definitionId);
|
|
1628
|
+
if (!kind) continue;
|
|
1629
|
+
const index = workspaceHandleIndex(edge?.sourceHandle, "output");
|
|
1630
|
+
const slot = output[index] || null;
|
|
1631
|
+
const name = String(slot?.name || "").trim() || (index === 0 ? "result" : `output-${index}`);
|
|
1632
|
+
bindings.push({
|
|
1633
|
+
kind,
|
|
1634
|
+
index,
|
|
1635
|
+
name,
|
|
1636
|
+
field: workspaceOutputFieldForSlot(slot, index),
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
return bindings;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1525
1642
|
function normalizeHtmlDisplayContent(content) {
|
|
1526
1643
|
let text = String(content || "").trim();
|
|
1527
1644
|
if (!text) return "";
|
|
@@ -1556,50 +1673,35 @@ function normalizeHtmlDisplayContent(content) {
|
|
|
1556
1673
|
}
|
|
1557
1674
|
|
|
1558
1675
|
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 "";
|
|
1676
|
+
const bindings = workspaceDownstreamOutputDisplayBindings(graph, nodeId);
|
|
1677
|
+
if (bindings.length === 0) return "";
|
|
1678
|
+
const seen = new Set();
|
|
1569
1679
|
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。');
|
|
1680
|
+
for (const binding of bindings) {
|
|
1681
|
+
const key = `${binding.field}:${binding.kind}`;
|
|
1682
|
+
if (seen.has(key)) continue;
|
|
1683
|
+
seen.add(key);
|
|
1684
|
+
rules.push(workspaceDisplayFieldRule(binding.kind, binding.field, binding.name));
|
|
1590
1685
|
}
|
|
1591
1686
|
return [
|
|
1592
1687
|
"## 下游输出要求",
|
|
1593
1688
|
"",
|
|
1594
1689
|
...rules,
|
|
1595
1690
|
"",
|
|
1691
|
+
"这些要求按输出引脚分别生效:不要把非 `result` 引脚连接的展示内容误写到 `result`;具名引脚应写入 `outParams.<引脚名>`。",
|
|
1596
1692
|
"如果用户任务与下游展示格式没有冲突,优先满足上述格式要求;如果用户明确指定了其他格式,以用户任务为准。",
|
|
1597
1693
|
].join("\n");
|
|
1598
1694
|
}
|
|
1599
1695
|
|
|
1600
1696
|
function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
1601
1697
|
const instance = graph?.instances?.[nodeId] || {};
|
|
1602
|
-
const
|
|
1698
|
+
const outputSlots = Array.isArray(instance.output) ? instance.output : [];
|
|
1699
|
+
const displayBindings = workspaceDownstreamOutputDisplayBindings(graph, nodeId);
|
|
1700
|
+
const displayByField = new Map();
|
|
1701
|
+
for (const binding of displayBindings) {
|
|
1702
|
+
if (!displayByField.has(binding.field)) displayByField.set(binding.field, binding.kind);
|
|
1703
|
+
}
|
|
1704
|
+
const slots = outputSlots
|
|
1603
1705
|
.filter((slot) => {
|
|
1604
1706
|
const name = String(slot?.name || "").trim();
|
|
1605
1707
|
const type = String(slot?.type || "");
|
|
@@ -1607,18 +1709,24 @@ function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
|
1607
1709
|
})
|
|
1608
1710
|
.map((slot) => String(slot.name).trim());
|
|
1609
1711
|
const outParamsExample = slots.length
|
|
1610
|
-
? Object.fromEntries(slots.map((name) => [name,
|
|
1712
|
+
? Object.fromEntries(slots.map((name) => [name, workspaceDisplayKindExample(displayByField.get(`outParams.${name}`) || "", name)]))
|
|
1611
1713
|
: {};
|
|
1714
|
+
const resultExample = workspaceDisplayKindExample(displayByField.get("result") || "markdown", "result");
|
|
1715
|
+
const slotDisplayRules = displayBindings
|
|
1716
|
+
.map((binding) => `- 输出引脚 \`${binding.name}\` -> ${binding.kind} 展示节点:写入 \`${binding.field}\`。`)
|
|
1717
|
+
.filter((line, index, arr) => arr.indexOf(line) === index);
|
|
1612
1718
|
return [
|
|
1613
1719
|
"## Workspace 输出协议",
|
|
1614
1720
|
"",
|
|
1615
|
-
"最终回复必须是一个 JSON
|
|
1721
|
+
"最终回复必须是一个 JSON 对象,必须直接以 `{` 开头并以 `}` 结尾;不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字、进度说明或自然语言前后缀。",
|
|
1722
|
+
"JSON 必须可被 `JSON.parse` 解析;如果 `result` 是多行 Markdown,必须在 JSON 字符串里使用 `\\n` 转义换行,不能把裸 Markdown 直接塞进未转义的字符串。",
|
|
1616
1723
|
"固定格式:",
|
|
1617
1724
|
"",
|
|
1618
|
-
JSON.stringify({ result:
|
|
1725
|
+
JSON.stringify({ result: resultExample, outParams: outParamsExample }, null, 2),
|
|
1619
1726
|
"",
|
|
1620
1727
|
"- `result`:完整正文,写入 `result` / `content` 输出口,直连默认展示节点时展示它。",
|
|
1621
1728
|
"- `outParams`:具名输出参数,只写入同名输出引脚。",
|
|
1729
|
+
...(slotDisplayRules.length ? ["- 当前输出引脚与展示节点映射:", ...slotDisplayRules] : []),
|
|
1622
1730
|
...(slots.length
|
|
1623
1731
|
? [`- 当前节点具名输出槽:${slots.map((name) => `\`${name}\``).join("、")}。例如任务要求写入 \`${slots[0]}\` 时,放到 \`outParams.${slots[0]}\`。`]
|
|
1624
1732
|
: ["- 当前节点没有额外具名输出槽,`outParams` 返回空对象即可。"]),
|