@fieldwangai/agentflow 0.1.167 → 0.1.168
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/agent-runners.mjs +77 -29
- package/bin/lib/builtin-node-review.mjs +101 -0
- package/bin/lib/repository-index.mjs +14 -2
- package/bin/lib/workspace-routes.mjs +17 -2
- package/bin/lib/workspace-run-logs.mjs +12 -1
- package/bin/lib/workspace-server.mjs +217 -22
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B0i4F0Ab.js → WorkflowAssistantThread-x1JCQpVD.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BPFVqIDM.css +1 -0
- package/builtin/web-ui/dist/assets/index-Y3sCNmlY.js +880 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +1 -1
- package/skills/agentflow-cli/runtime/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-BLTi7FF5.js +0 -877
- package/builtin/web-ui/dist/assets/index-yplDmRpj.css +0 -1
|
@@ -93,6 +93,28 @@ function cursorResultErrorText(event) {
|
|
|
93
93
|
return "";
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
export function isCursorAgentLoopingError(error = "") {
|
|
97
|
+
const text = String(error?.message || error || "");
|
|
98
|
+
return /agent looping detected|got stuck in a repeating response pattern/i.test(text);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isCursorReadOnlyToolCall(toolName = "") {
|
|
102
|
+
const name = String(toolName || "").trim();
|
|
103
|
+
if (!name) return false;
|
|
104
|
+
return /^(read|glob|grep|search|semanticSearch|list|find|fetch|webSearch|view|inspect)/i.test(name);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function annotateCursorFailure(error, { hadToolActivity = false, hadMutatingToolActivity = false } = {}) {
|
|
108
|
+
const failure = error instanceof Error ? error : new Error(String(error || "Cursor Agent failed."));
|
|
109
|
+
failure.cursorHadToolActivity = Boolean(hadToolActivity);
|
|
110
|
+
failure.cursorHadMutatingToolActivity = Boolean(hadMutatingToolActivity);
|
|
111
|
+
if (isCursorAgentLoopingError(failure)) {
|
|
112
|
+
failure.code = "CURSOR_AGENT_LOOPING";
|
|
113
|
+
failure.agentflowFailureCategory = "agent_looping";
|
|
114
|
+
}
|
|
115
|
+
return failure;
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
function envFlag(name, defaultValue = false) {
|
|
97
119
|
const raw = process.env[name];
|
|
98
120
|
if (raw == null || raw === "") return defaultValue;
|
|
@@ -459,6 +481,14 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
459
481
|
let lastResult = null;
|
|
460
482
|
let hadError = false;
|
|
461
483
|
let hadToolActivity = false;
|
|
484
|
+
let hadMutatingToolActivity = false;
|
|
485
|
+
const annotateFailure = (error) => {
|
|
486
|
+
const failure = annotateCursorFailure(error, { hadToolActivity, hadMutatingToolActivity });
|
|
487
|
+
failure.cursorModelLane = cursorModelSelection.lane;
|
|
488
|
+
failure.cursorModelId = cursorModelSelection.modelId;
|
|
489
|
+
failure.cursorModelName = cursorModelSelection.modelName;
|
|
490
|
+
return failure;
|
|
491
|
+
};
|
|
462
492
|
const STDERR_CAP_BYTES = 1024 * 1024;
|
|
463
493
|
const stderrChunks = [];
|
|
464
494
|
let stderrTotalBytes = 0;
|
|
@@ -546,6 +576,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
546
576
|
hadToolActivity = true;
|
|
547
577
|
const toolName =
|
|
548
578
|
event.tool_call && typeof event.tool_call === "object" ? Object.keys(event.tool_call)[0] ?? "?" : "?";
|
|
579
|
+
if (!isCursorReadOnlyToolCall(toolName)) hadMutatingToolActivity = true;
|
|
549
580
|
const subtype = event.subtype ?? "";
|
|
550
581
|
const statusLine = `工具 ${toolName}${subtype ? ` (${subtype})` : ""}`;
|
|
551
582
|
emit({ type: "status", line: statusLine });
|
|
@@ -583,13 +614,18 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
583
614
|
if (line.includes('"type":"tool_call"') || line.includes('"type": "tool_call"')) {
|
|
584
615
|
hadToolActivity = true;
|
|
585
616
|
let subtype = "?";
|
|
617
|
+
let toolName = "?";
|
|
586
618
|
try {
|
|
587
619
|
const ev = JSON.parse(line);
|
|
588
|
-
if (ev && ev.type === "tool_call")
|
|
620
|
+
if (ev && ev.type === "tool_call") {
|
|
621
|
+
subtype = ev.subtype ?? "?";
|
|
622
|
+
toolName = ev.tool_call && typeof ev.tool_call === "object" ? Object.keys(ev.tool_call)[0] ?? "?" : "?";
|
|
623
|
+
}
|
|
589
624
|
} catch {
|
|
590
625
|
const m = line.match(/"subtype"\s*:\s*"([^"]+)"/);
|
|
591
626
|
if (m) subtype = m[1];
|
|
592
627
|
}
|
|
628
|
+
if (!isCursorReadOnlyToolCall(toolName)) hadMutatingToolActivity = true;
|
|
593
629
|
emit({ type: "status", line: t("runner.tool_call", { subtype }) });
|
|
594
630
|
} else if (isLikelyBase64(line)) {
|
|
595
631
|
emit({ type: "status", line: t("runner.base64_data", { len: line.length }) });
|
|
@@ -624,44 +660,55 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
624
660
|
const rest = stderrComposerBuffer.trim();
|
|
625
661
|
emit({ type: "status", line: `[stderr] ${truncateComposerLine(rest)}` });
|
|
626
662
|
}
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if (!
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
663
|
+
const retryCursorFailure = (errorText) => {
|
|
664
|
+
const quotaFailure = isCursorQuotaError(errorText);
|
|
665
|
+
const loopingFailure = isCursorAgentLoopingError(errorText);
|
|
666
|
+
if (!quotaFailure && !loopingFailure) return false;
|
|
667
|
+
if (quotaFailure && hadToolActivity) return false;
|
|
668
|
+
if (loopingFailure && hadMutatingToolActivity) return false;
|
|
669
|
+
const errorCategory = quotaFailure ? classifyCursorApiKeyLimitError(errorText) : "agent_looping";
|
|
670
|
+
if (quotaFailure && cursorSelection) {
|
|
671
|
+
const cooldownMinutes = cursorApiKeyCooldownMinutes(cursorBaseEnv, errorText);
|
|
672
|
+
markCursorApiKeyLaneBlocked(
|
|
673
|
+
cursorSelection,
|
|
674
|
+
cursorModelSelection.lane,
|
|
675
|
+
cooldownMinutes,
|
|
676
|
+
errorText,
|
|
677
|
+
Date.now(),
|
|
678
|
+
{
|
|
679
|
+
modelId: cursorModelSelection.modelId,
|
|
680
|
+
modelName: cursorModelSelection.modelName,
|
|
681
|
+
},
|
|
682
|
+
);
|
|
683
|
+
}
|
|
644
684
|
const canTryComposer = !hasExplicitModel
|
|
645
685
|
&& cursorModelSelection.lane === "auto"
|
|
646
|
-
&&
|
|
647
|
-
|
|
648
|
-
|
|
686
|
+
&& (
|
|
687
|
+
loopingFailure
|
|
688
|
+
|| (errorCategory === "explicit_limit" && isCursorAutoFallbackEligible(errorText))
|
|
689
|
+
);
|
|
690
|
+
const hasNextKey = quotaFailure && Boolean(cursorSelection) && cursorAttemptIndex < cursorAttempts.length - 1;
|
|
649
691
|
if (!canTryComposer && !hasNextKey) return false;
|
|
650
692
|
|
|
651
693
|
const retry = async () => {
|
|
652
694
|
if (canTryComposer) {
|
|
695
|
+
const authLabel = cursorSelection
|
|
696
|
+
? `API Key ${cursorApiKeyLabel(cursorSelection)}`
|
|
697
|
+
: "login session";
|
|
653
698
|
emit({
|
|
654
699
|
type: "status",
|
|
655
|
-
line:
|
|
700
|
+
line: loopingFailure
|
|
701
|
+
? `Cursor Auto on ${authLabel} entered a response loop; discovering Composer fallback...`
|
|
702
|
+
: `Cursor Auto on ${authLabel} is out of usage; discovering Composer fallback...`,
|
|
656
703
|
});
|
|
657
704
|
const catalog = await discoverCursorModels({
|
|
658
|
-
keyId: cursorSelection.
|
|
705
|
+
keyId: cursorSelection?.id || `login:${cursorBaseEnv.AGENTFLOW_USER_ID || "default"}`,
|
|
659
706
|
cwd: ws,
|
|
660
707
|
command: agentCmd,
|
|
661
708
|
env: childEnv(options, cursorApiKeyEnv(cursorSelection)),
|
|
662
709
|
});
|
|
663
710
|
if (catalog.fallbackModel) {
|
|
664
|
-
recordCursorApiKeyFallbackModel(cursorSelection, catalog.fallbackModel);
|
|
711
|
+
if (cursorSelection) recordCursorApiKeyFallbackModel(cursorSelection, catalog.fallbackModel);
|
|
665
712
|
const fallbackSelection = {
|
|
666
713
|
lane: "fallback",
|
|
667
714
|
modelId: catalog.fallbackModel.id,
|
|
@@ -677,6 +724,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
677
724
|
stream: "runner",
|
|
678
725
|
eventType: "model_fallback",
|
|
679
726
|
text: `auto -> ${fallbackSelection.modelId}`,
|
|
727
|
+
reason: loopingFailure ? "agent_looping" : "usage_limit",
|
|
680
728
|
});
|
|
681
729
|
const fallback = runCursorAgentWithPrompt(
|
|
682
730
|
cliWorkspace,
|
|
@@ -705,7 +753,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
705
753
|
await next.finished;
|
|
706
754
|
return;
|
|
707
755
|
}
|
|
708
|
-
throw new Error(errorText || "Cursor API Key reached its limit.");
|
|
756
|
+
throw annotateFailure(new Error(errorText || (loopingFailure ? "Cursor Agent entered a response loop." : "Cursor API Key reached its limit.")));
|
|
709
757
|
};
|
|
710
758
|
retry().then(resolve).catch(reject);
|
|
711
759
|
return true;
|
|
@@ -713,9 +761,9 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
713
761
|
if (code !== 0 && lastResult == null) {
|
|
714
762
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
715
763
|
const stderrTail = stderr ? stderr.trim().slice(-1200) : "";
|
|
716
|
-
if (
|
|
764
|
+
if (retryCursorFailure(stderrTail)) return;
|
|
717
765
|
const stderrSummary = summarizeCursorStderr(stderr);
|
|
718
|
-
const err = new Error(`Cursor CLI exited ${code}. ${stderrSummary || "No result event received."}`);
|
|
766
|
+
const err = annotateFailure(new Error(`Cursor CLI exited ${code}. ${stderrSummary || "No result event received."}`));
|
|
719
767
|
err.cursorStderrTail = stderrTail;
|
|
720
768
|
emit({ type: "status", line: truncateComposerLine(err.message) });
|
|
721
769
|
reject(err);
|
|
@@ -723,9 +771,9 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
723
771
|
}
|
|
724
772
|
if (hadError || (lastResult && lastResult.is_error)) {
|
|
725
773
|
const msg = cursorResultErrorText(lastResult) || "Agent reported error.";
|
|
726
|
-
if (
|
|
774
|
+
if (retryCursorFailure(msg)) return;
|
|
727
775
|
emit({ type: "status", line: truncateComposerLine(msg) });
|
|
728
|
-
reject(new Error(msg));
|
|
776
|
+
reject(annotateFailure(new Error(msg)));
|
|
729
777
|
return;
|
|
730
778
|
}
|
|
731
779
|
if (cursorSelection) clearCursorApiKeyLaneCooldown(cursorSelection, cursorModelSelection.lane);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse as acornParse } from "acorn";
|
|
4
|
+
|
|
5
|
+
import { PACKAGE_ROOT } from "./paths.mjs";
|
|
6
|
+
|
|
7
|
+
const WORKSPACE_RUNTIME_PATH = "bin/lib/workspace-server.mjs";
|
|
8
|
+
|
|
9
|
+
// Only modules explicitly listed here are exposed in full. The runtime adapter is
|
|
10
|
+
// extracted from workspace-server.mjs so the review always follows the code that
|
|
11
|
+
// actually dispatches the built-in node without exposing the whole server file.
|
|
12
|
+
const BUILTIN_NODE_IMPLEMENTATION_FILES = new Map([
|
|
13
|
+
["tool_wecom_send_group_markdown", ["bin/lib/wecom.mjs"]],
|
|
14
|
+
["tool_wecom_send_app_markdown", ["bin/lib/wecom.mjs"]],
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const sourceCache = new Map();
|
|
18
|
+
let runtimeAstCache = null;
|
|
19
|
+
|
|
20
|
+
function readPackageSource(relativePath) {
|
|
21
|
+
const normalized = String(relativePath || "").replace(/\\/g, "/").replace(/^\/+/, "");
|
|
22
|
+
if (!normalized || normalized.includes("..")) return "";
|
|
23
|
+
if (sourceCache.has(normalized)) return sourceCache.get(normalized);
|
|
24
|
+
const absolutePath = path.join(PACKAGE_ROOT, normalized);
|
|
25
|
+
let source = "";
|
|
26
|
+
try {
|
|
27
|
+
if (fs.statSync(absolutePath).isFile()) source = fs.readFileSync(absolutePath, "utf8").replace(/\r\n/g, "\n");
|
|
28
|
+
} catch {}
|
|
29
|
+
sourceCache.set(normalized, source);
|
|
30
|
+
return source;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function walkAst(node, visit) {
|
|
34
|
+
if (!node || typeof node !== "object") return;
|
|
35
|
+
if (typeof node.type === "string") visit(node);
|
|
36
|
+
for (const [key, value] of Object.entries(node)) {
|
|
37
|
+
if (["start", "end", "loc", "type"].includes(key)) continue;
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
for (const child of value) walkAst(child, visit);
|
|
40
|
+
} else if (value && typeof value === "object") {
|
|
41
|
+
walkAst(value, visit);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function astContainsString(node, expected) {
|
|
47
|
+
let found = false;
|
|
48
|
+
walkAst(node, (candidate) => {
|
|
49
|
+
if (candidate.type === "Literal" && candidate.value === expected) found = true;
|
|
50
|
+
});
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function runtimeAst() {
|
|
55
|
+
const source = readPackageSource(WORKSPACE_RUNTIME_PATH);
|
|
56
|
+
if (!source) return { source: "", ast: null };
|
|
57
|
+
if (runtimeAstCache?.source === source) return runtimeAstCache;
|
|
58
|
+
let ast = null;
|
|
59
|
+
try {
|
|
60
|
+
ast = acornParse(source, { ecmaVersion: "latest", sourceType: "module", allowHashBang: true });
|
|
61
|
+
} catch {}
|
|
62
|
+
runtimeAstCache = { source, ast };
|
|
63
|
+
return runtimeAstCache;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function runtimeAdapterSource(definitionId) {
|
|
67
|
+
const { source, ast } = runtimeAst();
|
|
68
|
+
if (!source || !ast) return "";
|
|
69
|
+
const matches = [];
|
|
70
|
+
walkAst(ast, (node) => {
|
|
71
|
+
if (node.type !== "IfStatement" || !astContainsString(node.test, definitionId)) return;
|
|
72
|
+
matches.push(source.slice(node.start, node.end).trim());
|
|
73
|
+
});
|
|
74
|
+
return matches.join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function builtinNodeReviewSources(definitionId = "") {
|
|
78
|
+
const id = String(definitionId || "").trim();
|
|
79
|
+
if (!id) return [];
|
|
80
|
+
const sources = [];
|
|
81
|
+
const adapter = runtimeAdapterSource(id);
|
|
82
|
+
if (adapter) {
|
|
83
|
+
sources.push({
|
|
84
|
+
sourcePath: `builtin/${id}/runtime-adapter.mjs`,
|
|
85
|
+
title: "内置运行适配器",
|
|
86
|
+
kind: "builtin-adapter",
|
|
87
|
+
content: adapter,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const relativePath of BUILTIN_NODE_IMPLEMENTATION_FILES.get(id) || []) {
|
|
91
|
+
const content = readPackageSource(relativePath);
|
|
92
|
+
if (!content) continue;
|
|
93
|
+
sources.push({
|
|
94
|
+
sourcePath: relativePath,
|
|
95
|
+
title: `内置实现 · ${path.basename(relativePath)}`,
|
|
96
|
+
kind: "builtin-implementation",
|
|
97
|
+
content,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return sources;
|
|
101
|
+
}
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
workspaceRunPlan,
|
|
21
21
|
} from "./workspace-server.mjs";
|
|
22
22
|
|
|
23
|
-
const REPOSITORY_INDEX_VERSION =
|
|
23
|
+
const REPOSITORY_INDEX_VERSION = 2;
|
|
24
24
|
const REPOSITORY_INDEX_FILENAME = "repository-index.json";
|
|
25
25
|
const REPOSITORY_INDEX_MAX_AGE_MS = 5 * 60 * 1000;
|
|
26
26
|
const memoryIndexes = new Map();
|
|
@@ -167,10 +167,12 @@ function scanProjectFlows(workspaceRoot, usageStats) {
|
|
|
167
167
|
const appendFlow = (ownerId, flow, flowSource = "user", workspaceId = "") => {
|
|
168
168
|
if (!ownerId || flow.archived || !flow.path) return;
|
|
169
169
|
let stable;
|
|
170
|
+
let draftGraph;
|
|
170
171
|
let graph;
|
|
171
172
|
try {
|
|
173
|
+
draftGraph = readWorkspaceGraph(flow.path, workspaceRoot).graph;
|
|
172
174
|
stable = readWorkspaceStableRelease(flow.path, workspaceRoot);
|
|
173
|
-
graph = stable?.graph ||
|
|
175
|
+
graph = stable?.graph || draftGraph;
|
|
174
176
|
} catch {
|
|
175
177
|
return;
|
|
176
178
|
}
|
|
@@ -181,6 +183,13 @@ function scanProjectFlows(workspaceRoot, usageStats) {
|
|
|
181
183
|
const baseId = projectFlowRepositoryId(ownerId, flowSource, flow.id);
|
|
182
184
|
const id = runnableEntries.length === 1 ? baseId : `${baseId}:${runnable.entryId}`;
|
|
183
185
|
const version = stable?.release?.id || `current-${workspaceDesignRevision(graph).slice(0, 12)}`;
|
|
186
|
+
const releaseState = stable?.release?.id ? "stable" : "draft";
|
|
187
|
+
const draftRevision = workspaceDesignRevision(draftGraph);
|
|
188
|
+
const stableRevisions = new Set([
|
|
189
|
+
stable?.release?.designRevision || "",
|
|
190
|
+
stable?.graph ? workspaceDesignRevision(stable.graph) : "",
|
|
191
|
+
].filter(Boolean));
|
|
192
|
+
const hasUnpublishedChanges = Boolean(stable?.release?.id && !stableRevisions.has(draftRevision));
|
|
184
193
|
const rawEntryLabel = String(runnable.entry?.label || "").trim();
|
|
185
194
|
const genericLabel = ["", "Run", "Scheduled Run", "运行", "定时运行"].includes(rawEntryLabel);
|
|
186
195
|
const exactOwner = flowSource === "user" ? ownerId : "";
|
|
@@ -200,6 +209,9 @@ function scanProjectFlows(workspaceRoot, usageStats) {
|
|
|
200
209
|
description: flow.description || "",
|
|
201
210
|
version,
|
|
202
211
|
versionLabel: stable?.release?.id ? `Stable ${stable.release.id}` : "当前版本",
|
|
212
|
+
releaseState,
|
|
213
|
+
stableReleaseId: stable?.release?.id || "",
|
|
214
|
+
hasUnpublishedChanges,
|
|
203
215
|
runMode: runnable.runMode,
|
|
204
216
|
runModeLabel: runnable.runMode === "scheduled" ? "定时运行" : "手动运行",
|
|
205
217
|
ownerUserId: ownerId,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
writeAiExplorationMaterialization,
|
|
28
28
|
} from "./ai-exploration.mjs";
|
|
29
29
|
import { buildSkillCompactInjectionBlock, loadResourcesForSkillKeys } from "./composer-skill-router.mjs";
|
|
30
|
+
import { builtinNodeReviewSources } from "./builtin-node-review.mjs";
|
|
30
31
|
import { execFileBuffered } from "./exec-buffered.mjs";
|
|
31
32
|
import { runGit } from "./git-worktree.mjs";
|
|
32
33
|
import {
|
|
@@ -393,6 +394,10 @@ function workspaceNodeReviewSnapshot(workspaceRoot, flowRoot, graph, nodeId, use
|
|
|
393
394
|
}));
|
|
394
395
|
}
|
|
395
396
|
|
|
397
|
+
if (!marketplaceRef.startsWith("marketplace:")) {
|
|
398
|
+
sources.push(...builtinNodeReviewSources(definitionId).map((source) => workspaceNodeReviewSource(source)));
|
|
399
|
+
}
|
|
400
|
+
|
|
396
401
|
for (const [ref, kind] of [[instance.scriptRef, "script"], [instance.implementationRef, "implementation"]]) {
|
|
397
402
|
const source = workspaceNodeReviewFlowFile(flowRoot, ref, kind);
|
|
398
403
|
if (source?.error) errors.push(source.error);
|
|
@@ -1102,7 +1107,7 @@ function listRunnableProjectMarketplaceFlows(workspaceRoot, userCtx = {}, scope
|
|
|
1102
1107
|
}
|
|
1103
1108
|
|
|
1104
1109
|
function publicProjectFlowMarketplaceResource(resource) {
|
|
1105
|
-
const { _graph, _flowRoot, ...publicResource } = resource;
|
|
1110
|
+
const { _graph, _flowRoot, flowRoot, ...publicResource } = resource;
|
|
1106
1111
|
return publicResource;
|
|
1107
1112
|
}
|
|
1108
1113
|
|
|
@@ -1258,6 +1263,8 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1258
1263
|
const kind = String(url.searchParams.get("kind") || "flow").trim();
|
|
1259
1264
|
const requestedScope = String(url.searchParams.get("scope") || "all").trim();
|
|
1260
1265
|
const scope = ["all", "owned", "installed"].includes(requestedScope) ? requestedScope : "all";
|
|
1266
|
+
const requestedReleaseState = String(url.searchParams.get("releaseState") || "all").trim().toLowerCase();
|
|
1267
|
+
const releaseState = ["all", "stable", "draft"].includes(requestedReleaseState) ? requestedReleaseState : "all";
|
|
1261
1268
|
const marketplaceScope = scope === "owned" ? "owned" : "all";
|
|
1262
1269
|
const queryText = String(url.searchParams.get("q") || "").trim().toLowerCase();
|
|
1263
1270
|
if (kind === "node") {
|
|
@@ -1320,6 +1327,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1320
1327
|
const items = sortMarketplaceResources(
|
|
1321
1328
|
projectFlows
|
|
1322
1329
|
.filter((item) => scope !== "installed" || item.installed)
|
|
1330
|
+
.filter((item) => releaseState === "all" || item.releaseState === releaseState)
|
|
1323
1331
|
.filter((item) => marketplaceResourceMatches(item, queryText)),
|
|
1324
1332
|
);
|
|
1325
1333
|
const page = paginateMarketplaceResources(items, url);
|
|
@@ -1327,6 +1335,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1327
1335
|
json(res, 200, {
|
|
1328
1336
|
kind,
|
|
1329
1337
|
scope,
|
|
1338
|
+
releaseState,
|
|
1330
1339
|
sort: "useCount",
|
|
1331
1340
|
order: "desc",
|
|
1332
1341
|
...page,
|
|
@@ -1568,7 +1577,11 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1568
1577
|
marketplaceAction: action,
|
|
1569
1578
|
marketplaceInstallFlowId: resource.installFlowId || resource.liveFlowId || resource.definitionId || id,
|
|
1570
1579
|
});
|
|
1571
|
-
if (previewKind === "node")
|
|
1580
|
+
if (previewKind === "node") {
|
|
1581
|
+
previewParams.set("focusNodeId", "node_preview");
|
|
1582
|
+
} else if (projectFlow && resource.liveEntryId) {
|
|
1583
|
+
previewParams.set("focusNodeId", resource.liveEntryId);
|
|
1584
|
+
}
|
|
1572
1585
|
if (action === "open-source") {
|
|
1573
1586
|
previewParams.set("marketplaceTargetFlowId", resource.liveFlowId || resource.definitionId || "");
|
|
1574
1587
|
previewParams.set("marketplaceTargetFlowSource", resource.liveFlowSource || "user");
|
|
@@ -3353,6 +3366,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
3353
3366
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3354
3367
|
flowId,
|
|
3355
3368
|
flowSource: flowSource || "user",
|
|
3369
|
+
adminOwnerId: url.searchParams.get("adminOwnerId") || "",
|
|
3356
3370
|
archived: url.searchParams.get("archived") === "1",
|
|
3357
3371
|
}, userCtx);
|
|
3358
3372
|
if (scoped.error) {
|
|
@@ -3388,6 +3402,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
3388
3402
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3389
3403
|
flowId,
|
|
3390
3404
|
flowSource,
|
|
3405
|
+
adminOwnerId: url.searchParams.get("adminOwnerId") || "",
|
|
3391
3406
|
archived: url.searchParams.get("archived") === "1",
|
|
3392
3407
|
}, userCtx);
|
|
3393
3408
|
if (scoped.error) {
|
|
@@ -7,6 +7,11 @@ import { runLedgerId } from "./run-ledger.mjs";
|
|
|
7
7
|
const MAX_EVENT_TEXT_CHARS = 20_000;
|
|
8
8
|
const MAX_INDEX_RECORDS = 10_000;
|
|
9
9
|
const SECRET_KEY_RE = /(token|password|passwd|secret|webhook|authorization|api[_-]?key|access[_-]?key)/i;
|
|
10
|
+
const SECRET_TEXT_PATTERNS = [
|
|
11
|
+
[/\b(Bearer)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 ***"],
|
|
12
|
+
[/((?:token|password|passwd|secret|authorization|api[_-]?key|access[_-]?key)\s*[=:]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi, "$1***"],
|
|
13
|
+
[/(["'](?:token|password|passwd|secret|authorization|api[_-]?key|access[_-]?key)["']\s*:\s*)("[^"]*"|'[^']*')/gi, "$1\"***\""],
|
|
14
|
+
];
|
|
10
15
|
|
|
11
16
|
function safeSegment(value, fallback = "run") {
|
|
12
17
|
return String(value || fallback)
|
|
@@ -34,9 +39,15 @@ function truncateText(value) {
|
|
|
34
39
|
return `${text.slice(0, MAX_EVENT_TEXT_CHARS)}\n... [truncated ${text.length - MAX_EVENT_TEXT_CHARS} chars]`;
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
function redactText(value) {
|
|
43
|
+
let text = truncateText(value);
|
|
44
|
+
for (const [pattern, replacement] of SECRET_TEXT_PATTERNS) text = text.replace(pattern, replacement);
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
function redact(value, depth = 0) {
|
|
38
49
|
if (depth > 8) return "[MaxDepth]";
|
|
39
|
-
if (typeof value === "string") return
|
|
50
|
+
if (typeof value === "string") return redactText(value);
|
|
40
51
|
if (value == null || typeof value === "number" || typeof value === "boolean") return value;
|
|
41
52
|
if (Array.isArray(value)) return value.slice(0, 200).map((item) => redact(item, depth + 1));
|
|
42
53
|
if (typeof value === "object") {
|