@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.
@@ -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) return { result: raw, outParams: {}, structured: false, parsed: null };
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 instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
1560
- const edges = Array.isArray(graph?.edges) ? graph.edges : [];
1561
- const kinds = new Set();
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
- if (kinds.has("html")) {
1571
- rules.push("- 下游连接了 HTML 展示节点:将可直接放入 iframe 渲染的 HTML 放在输出协议的 `result` 字段中。可以是完整 HTML 文档或 HTML fragment;不要使用 Markdown 代码围栏。");
1572
- }
1573
- if (kinds.has("markdown")) {
1574
- rules.push("- 下游连接了 Markdown 展示节点:将 Markdown 正文放在输出协议的 `result` 字段中;除非正文确实需要代码块,否则不要额外包裹代码围栏。");
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 slots = (Array.isArray(instance.output) ? instance.output : [])
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, `<${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 对象,不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字。",
1721
+ "最终回复必须是一个 JSON 对象,必须直接以 `{` 开头并以 `}` 结尾;不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字、进度说明或自然语言前后缀。",
1722
+ "JSON 必须可被 `JSON.parse` 解析;如果 `result` 是多行 Markdown,必须在 JSON 字符串里使用 `\\n` 转义换行,不能把裸 Markdown 直接塞进未转义的字符串。",
1616
1723
  "固定格式:",
1617
1724
  "",
1618
- JSON.stringify({ result: "<给用户看的完整正文>", outParams: outParamsExample }, null, 2),
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` 返回空对象即可。"]),