@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.
@@ -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) return { result: raw, outParams: {}, structured: false, parsed: null };
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 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 "";
1803
+ const bindings = workspaceDownstreamOutputDisplayBindings(graph, nodeId);
1804
+ if (bindings.length === 0) return "";
1805
+ const seen = new Set();
1569
1806
  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。');
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 slots = (Array.isArray(instance.output) ? instance.output : [])
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, `<${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 对象,不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字。",
1848
+ "最终回复必须是一个 JSON 对象,必须直接以 `{` 开头并以 `}` 结尾;不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字、进度说明或自然语言前后缀。",
1849
+ "JSON 必须可被 `JSON.parse` 解析;如果 `result` 是多行 Markdown,必须在 JSON 字符串里使用 `\\n` 转义换行,不能把裸 Markdown 直接塞进未转义的字符串。",
1616
1850
  "固定格式:",
1617
1851
  "",
1618
- JSON.stringify({ result: "<给用户看的完整正文>", outParams: outParamsExample }, null, 2),
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` 返回空对象即可。"]),