@fieldwangai/agentflow 0.1.166 → 0.1.167
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 +27 -9
- package/bin/lib/ai-exploration.mjs +293 -0
- package/bin/lib/composer-agent.mjs +11 -0
- package/bin/lib/cursor-api-key-pool.mjs +106 -1
- package/bin/lib/repository-index-events.mjs +17 -0
- package/bin/lib/repository-index.mjs +522 -0
- package/bin/lib/ui-server.mjs +167 -0
- package/bin/lib/workspace-routes.mjs +543 -210
- package/bin/lib/workspace-server.mjs +2 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-DKdBGu3q.js → WorkflowAssistantThread-B0i4F0Ab.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BLTi7FF5.js +877 -0
- package/builtin/web-ui/dist/assets/index-yplDmRpj.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-ai-exploration/SKILL.md +127 -0
- package/skills/agentflow-ai-exploration/agents/openai.yaml +4 -0
- package/skills/agentflow-ai-exploration/references/protocol.md +120 -0
- package/skills/agentflow-ai-exploration/scripts/agentflow-ai-exploration.mjs +308 -0
- package/skills/agentflow-ai-exploration/scripts/auth-store.mjs +102 -0
- 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-B7YuvFR2.css +0 -1
- package/builtin/web-ui/dist/assets/index-BUljbvrW.js +0 -877
|
@@ -11,8 +11,21 @@
|
|
|
11
11
|
* 是请求回调的闭包,解构之后路由体里的写法完全不变。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { listNodesJson, readNodeDetailJson, readNodeFilePreview } from "./catalog-flows.mjs";
|
|
15
15
|
import { startComposerAgent } from "./composer-agent.mjs";
|
|
16
|
+
import {
|
|
17
|
+
aiMaterializationPrompt,
|
|
18
|
+
aiPlanPrompt,
|
|
19
|
+
appendAiTraceEvents,
|
|
20
|
+
createAiExplorationSession,
|
|
21
|
+
classifyAiToolSideEffect,
|
|
22
|
+
listAiExplorationSessions,
|
|
23
|
+
materializableAiTraceEvents,
|
|
24
|
+
parseAiPlanResult,
|
|
25
|
+
readAiExplorationSession,
|
|
26
|
+
updateAiExplorationSession,
|
|
27
|
+
writeAiExplorationMaterialization,
|
|
28
|
+
} from "./ai-exploration.mjs";
|
|
16
29
|
import { buildSkillCompactInjectionBlock, loadResourcesForSkillKeys } from "./composer-skill-router.mjs";
|
|
17
30
|
import { execFileBuffered } from "./exec-buffered.mjs";
|
|
18
31
|
import { runGit } from "./git-worktree.mjs";
|
|
@@ -30,28 +43,36 @@ import {
|
|
|
30
43
|
} from "./marketplace.mjs";
|
|
31
44
|
import {
|
|
32
45
|
appendMarketplaceUsageEvent,
|
|
33
|
-
marketplaceStatsFor,
|
|
34
|
-
marketplaceUsageStats,
|
|
35
46
|
marketplaceResourcesForRun,
|
|
36
|
-
readMarketplaceFlowOrigin,
|
|
37
47
|
writeMarketplaceFlowOrigin,
|
|
38
48
|
} from "./marketplace-usage.mjs";
|
|
49
|
+
import {
|
|
50
|
+
indexedProjectFlowPreview,
|
|
51
|
+
getRepositoryIndex,
|
|
52
|
+
indexedMarketplaceFlowCopies,
|
|
53
|
+
listIndexedNodes,
|
|
54
|
+
listIndexedProjectFlows,
|
|
55
|
+
markRepositoryIndexDirty,
|
|
56
|
+
updateIndexedProjectFlowVisibility,
|
|
57
|
+
updateIndexedNodeVisibility,
|
|
58
|
+
writeProjectFlowMarketplaceMetadata,
|
|
59
|
+
} from "./repository-index.mjs";
|
|
39
60
|
import { NODE_PACKAGE_ENTRY, nodePackageExportsRun, readNodePackageManifest } from "./node-package-manifest.mjs";
|
|
40
61
|
import { inspectNodePackageDirectory } from "./node-package-archive.mjs";
|
|
41
62
|
import { json, readBody } from "./http-util.mjs";
|
|
42
63
|
import { log } from "./log.mjs";
|
|
43
|
-
import { PACKAGE_ROOT, PIPELINES_DIR, getAgentflowUserDataRoot, getUserPipelinesRoot
|
|
64
|
+
import { PACKAGE_ROOT, PIPELINES_DIR, getAgentflowUserDataRoot, getUserPipelinesRoot } from "./paths.mjs";
|
|
44
65
|
import { validateUserPipelineId } from "./flow-write.mjs";
|
|
45
66
|
import { runLedgerId } from "./run-ledger.mjs";
|
|
46
67
|
import { getTeamById, getTeamForUser } from "./teams.mjs";
|
|
47
68
|
import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
|
|
48
|
-
import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, deleteWorkspaceCollaborationForFlow, ensureWorkspaceCollaboration,
|
|
69
|
+
import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, deleteWorkspaceCollaborationForFlow, ensureWorkspaceCollaboration, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
|
|
49
70
|
import { WorkspaceFlowParseError } from "./workspace-flow-store.mjs";
|
|
50
71
|
import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision } from "./workspace-graph-merge.mjs";
|
|
51
72
|
import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
|
|
52
73
|
import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
|
|
53
74
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
|
|
54
|
-
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, publishWorkspaceRelease, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, readWorkspaceReleaseStatus,
|
|
75
|
+
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, publishWorkspaceRelease, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, readWorkspaceReleaseStatus, readWorkspaceStableRelease, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, rollbackWorkspaceRelease, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, upsertWorkspaceDeferredRun, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDeferredRunsForScope, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScheduleNextRunAt, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
|
|
55
76
|
import { splitWorkspaceGraph, WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
56
77
|
import { getWorkspaceTree } from "./workspace-tree.mjs";
|
|
57
78
|
import busboy from "busboy";
|
|
@@ -1076,196 +1097,8 @@ function workspaceBufferLooksLikeZip(buf) {
|
|
|
1076
1097
|
);
|
|
1077
1098
|
}
|
|
1078
1099
|
|
|
1079
|
-
function installedMarketplaceFlowCopies(userId) {
|
|
1080
|
-
const copies = new Map();
|
|
1081
|
-
const pipelinesRoot = getUserPipelinesRoot(userId);
|
|
1082
|
-
if (!fs.existsSync(pipelinesRoot)) return copies;
|
|
1083
|
-
for (const entry of fs.readdirSync(pipelinesRoot, { withFileTypes: true })) {
|
|
1084
|
-
if (!entry.isDirectory()) continue;
|
|
1085
|
-
const origin = readMarketplaceFlowOrigin(path.join(pipelinesRoot, entry.name));
|
|
1086
|
-
if (!origin) continue;
|
|
1087
|
-
const key = `${origin.id}@${origin.version}`;
|
|
1088
|
-
const flowIds = copies.get(key) || [];
|
|
1089
|
-
flowIds.push(entry.name);
|
|
1090
|
-
copies.set(key, flowIds);
|
|
1091
|
-
}
|
|
1092
|
-
return copies;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
const PROJECT_FLOW_MARKETPLACE_FILENAME = path.join(".workspace", "agentflow", "marketplace.json");
|
|
1096
|
-
|
|
1097
|
-
function projectFlowMarketplaceId(ownerUserId, flowSource, flowId) {
|
|
1098
|
-
return `project-flow:${String(ownerUserId || "").trim()}:${String(flowSource || "user").trim()}:${String(flowId || "").trim()}`;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
function projectFlowMarketplaceMetadataPath(flowRoot) {
|
|
1102
|
-
return path.join(path.resolve(flowRoot), PROJECT_FLOW_MARKETPLACE_FILENAME);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
function readProjectFlowMarketplaceMetadata(flowRoot) {
|
|
1106
|
-
try {
|
|
1107
|
-
const parsed = JSON.parse(fs.readFileSync(projectFlowMarketplaceMetadataPath(flowRoot), "utf-8"));
|
|
1108
|
-
return {
|
|
1109
|
-
visibility: String(parsed?.visibility || "").trim() === "private" ? "private" : "public",
|
|
1110
|
-
updatedAt: String(parsed?.updatedAt || "").trim(),
|
|
1111
|
-
};
|
|
1112
|
-
} catch {
|
|
1113
|
-
return { visibility: "public", updatedAt: "" };
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
function writeProjectFlowMarketplaceMetadata(flowRoot, visibility) {
|
|
1118
|
-
const filePath = projectFlowMarketplaceMetadataPath(flowRoot);
|
|
1119
|
-
const value = {
|
|
1120
|
-
version: 1,
|
|
1121
|
-
visibility: String(visibility || "").trim() === "private" ? "private" : "public",
|
|
1122
|
-
updatedAt: new Date().toISOString(),
|
|
1123
|
-
};
|
|
1124
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1125
|
-
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
1126
|
-
fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
1127
|
-
fs.renameSync(tempPath, filePath);
|
|
1128
|
-
return value;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
function runnableProjectFlowEntries(graph, scopedRoot = "") {
|
|
1132
|
-
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1133
|
-
const entries = Object.entries(instances).filter(([, instance]) => (
|
|
1134
|
-
instance?.definitionId === "workspace_run"
|
|
1135
|
-
|| instance?.definitionId === "workspace_scheduled_run"
|
|
1136
|
-
));
|
|
1137
|
-
return entries.flatMap(([entryId, entry]) => {
|
|
1138
|
-
let plan;
|
|
1139
|
-
try {
|
|
1140
|
-
plan = workspaceRunPlan(graph, entryId, scopedRoot, { ignoreCache: true });
|
|
1141
|
-
} catch {
|
|
1142
|
-
return [];
|
|
1143
|
-
}
|
|
1144
|
-
const executedNodeIds = Array.from(new Set((plan.order || []).map((id) => String(id || "").trim()).filter(Boolean)));
|
|
1145
|
-
if (executedNodeIds.length === 0) return [];
|
|
1146
|
-
const includedNodeIds = new Set([entryId, ...executedNodeIds]);
|
|
1147
|
-
const positions = graph?.ui?.nodePositions && typeof graph.ui.nodePositions === "object"
|
|
1148
|
-
? Object.fromEntries(Object.entries(graph.ui.nodePositions).filter(([id]) => includedNodeIds.has(id)))
|
|
1149
|
-
: {};
|
|
1150
|
-
const sizes = graph?.ui?.nodeSizes && typeof graph.ui.nodeSizes === "object"
|
|
1151
|
-
? Object.fromEntries(Object.entries(graph.ui.nodeSizes).filter(([id]) => includedNodeIds.has(id)))
|
|
1152
|
-
: {};
|
|
1153
|
-
return [{
|
|
1154
|
-
entryId,
|
|
1155
|
-
entry,
|
|
1156
|
-
runMode: entry.definitionId === "workspace_scheduled_run" ? "scheduled" : "manual",
|
|
1157
|
-
graph: {
|
|
1158
|
-
...graph,
|
|
1159
|
-
instances: Object.fromEntries(Object.entries(instances).filter(([id]) => includedNodeIds.has(id))),
|
|
1160
|
-
edges: (Array.isArray(graph?.edges) ? graph.edges : []).filter((edge) => (
|
|
1161
|
-
includedNodeIds.has(String(edge?.source || ""))
|
|
1162
|
-
&& includedNodeIds.has(String(edge?.target || ""))
|
|
1163
|
-
)),
|
|
1164
|
-
ui: {
|
|
1165
|
-
...(graph?.ui || {}),
|
|
1166
|
-
nodePositions: positions,
|
|
1167
|
-
nodeSizes: sizes,
|
|
1168
|
-
},
|
|
1169
|
-
},
|
|
1170
|
-
}];
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
function projectFlowUpdatedAt(flowRoot, metadata = {}) {
|
|
1175
|
-
if (metadata.updatedAt) return metadata.updatedAt;
|
|
1176
|
-
try {
|
|
1177
|
-
return fs.statSync(path.resolve(flowRoot)).mtime.toISOString();
|
|
1178
|
-
} catch {
|
|
1179
|
-
return "";
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
1100
|
function listRunnableProjectMarketplaceFlows(workspaceRoot, userCtx = {}, scope = "all") {
|
|
1184
|
-
|
|
1185
|
-
const stats = marketplaceUsageStats(workspaceRoot);
|
|
1186
|
-
const runUsage = readWorkspaceRunUsageRecords();
|
|
1187
|
-
const resources = [];
|
|
1188
|
-
const appendFlow = (ownerId, flow, flowSource = "user", workspaceId = "") => {
|
|
1189
|
-
if (!ownerId || (scope === "owned" && ownerId !== requestedUserId)) return;
|
|
1190
|
-
if (flow.archived || !flow.path) return;
|
|
1191
|
-
let stable;
|
|
1192
|
-
let graph;
|
|
1193
|
-
try {
|
|
1194
|
-
stable = readWorkspaceStableRelease(flow.path, workspaceRoot);
|
|
1195
|
-
graph = stable?.graph || readWorkspaceGraph(flow.path, workspaceRoot).graph;
|
|
1196
|
-
} catch {
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
const runnableEntries = runnableProjectFlowEntries(graph, flow.path);
|
|
1200
|
-
if (runnableEntries.length === 0) return;
|
|
1201
|
-
const metadata = readProjectFlowMarketplaceMetadata(flow.path);
|
|
1202
|
-
const owned = ownerId === requestedUserId;
|
|
1203
|
-
if (scope !== "owned" && metadata.visibility === "private" && !owned && userCtx.isAdmin !== true) return;
|
|
1204
|
-
for (const runnable of runnableEntries) {
|
|
1205
|
-
const baseId = projectFlowMarketplaceId(ownerId, flowSource, flow.id);
|
|
1206
|
-
const id = runnableEntries.length === 1 ? baseId : `${baseId}:${runnable.entryId}`;
|
|
1207
|
-
const version = stable?.release?.id || `current-${workspaceDesignRevision(graph).slice(0, 12)}`;
|
|
1208
|
-
const rawEntryLabel = String(runnable.entry?.label || "").trim();
|
|
1209
|
-
const genericLabel = ["", "Run", "Scheduled Run", "运行", "定时运行"].includes(rawEntryLabel);
|
|
1210
|
-
const directRuns = runUsage.filter((run) => (
|
|
1211
|
-
run.status === "success"
|
|
1212
|
-
&& run.flowId === flow.id
|
|
1213
|
-
&& (run.flowSource || "user") === flowSource
|
|
1214
|
-
&& (flowSource !== "user" || run.userId === ownerId)
|
|
1215
|
-
&& (run.runNodeId ? run.runNodeId === runnable.entryId : runnableEntries.length === 1)
|
|
1216
|
-
));
|
|
1217
|
-
const telemetry = marketplaceStatsFor(stats, "project-flow", id, version);
|
|
1218
|
-
const directLastUsedAt = directRuns.reduce((latest, run) => {
|
|
1219
|
-
const value = new Date(Number(run.endedAt || run.at || 0)).toISOString();
|
|
1220
|
-
return !latest || value > latest ? value : latest;
|
|
1221
|
-
}, "");
|
|
1222
|
-
resources.push({
|
|
1223
|
-
resourceType: "flow",
|
|
1224
|
-
projectFlow: true,
|
|
1225
|
-
id,
|
|
1226
|
-
definitionId: `${flow.id}/${runnable.entryId}`,
|
|
1227
|
-
displayName: runnableEntries.length === 1
|
|
1228
|
-
? flow.id
|
|
1229
|
-
: `${flow.id} · ${genericLabel ? runnable.entryId : rawEntryLabel}`,
|
|
1230
|
-
description: flow.description || "",
|
|
1231
|
-
version,
|
|
1232
|
-
versionLabel: stable?.release?.id ? `Stable ${stable.release.id}` : "当前版本",
|
|
1233
|
-
runMode: runnable.runMode,
|
|
1234
|
-
runModeLabel: runnable.runMode === "scheduled" ? "定时运行" : "手动运行",
|
|
1235
|
-
ownerUserId: ownerId,
|
|
1236
|
-
liveOwnerUserId: ownerId,
|
|
1237
|
-
liveFlowId: flow.id,
|
|
1238
|
-
liveFlowSource: flowSource,
|
|
1239
|
-
liveWorkspaceId: workspaceId,
|
|
1240
|
-
liveEntryId: runnable.entryId,
|
|
1241
|
-
installFlowId: runnableEntries.length === 1 ? flow.id : `${flow.id}-${runnable.entryId}`,
|
|
1242
|
-
visibility: metadata.visibility,
|
|
1243
|
-
nodeCount: Object.keys(runnable.graph?.instances || {}).length,
|
|
1244
|
-
edgeCount: Array.isArray(runnable.graph?.edges) ? runnable.graph.edges.length : 0,
|
|
1245
|
-
updatedAt: projectFlowUpdatedAt(flow.path, metadata),
|
|
1246
|
-
...telemetry,
|
|
1247
|
-
useCount: telemetry.useCount + directRuns.length,
|
|
1248
|
-
lastUsedAt: [telemetry.lastUsedAt, directLastUsedAt].filter(Boolean).sort().at(-1) || "",
|
|
1249
|
-
_graph: runnable.graph,
|
|
1250
|
-
_flowRoot: flow.path,
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
};
|
|
1254
|
-
for (const ownerUserId of listAgentflowUserIds()) {
|
|
1255
|
-
const ownerId = String(ownerUserId || "").trim();
|
|
1256
|
-
for (const flow of listFlowsJson(workspaceRoot, { userId: ownerId })) {
|
|
1257
|
-
if ((flow.source || "user") !== "user" || flow.archived || !flow.path) continue;
|
|
1258
|
-
appendFlow(ownerId, flow, "user");
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
for (const flow of listFlowsJson(workspaceRoot, { userId: "", includeWorkspaceFlows: true })) {
|
|
1262
|
-
if ((flow.source || "") !== "workspace" || flow.archived || !flow.path) continue;
|
|
1263
|
-
const collaboration = getWorkspaceCollaborationByFlow(flow.id, false);
|
|
1264
|
-
const ownerId = String(collaboration?.ownerId || "").trim();
|
|
1265
|
-
if (!ownerId) continue;
|
|
1266
|
-
appendFlow(ownerId, flow, "workspace", collaboration.id);
|
|
1267
|
-
}
|
|
1268
|
-
return resources;
|
|
1101
|
+
return listIndexedProjectFlows(workspaceRoot, userCtx, scope);
|
|
1269
1102
|
}
|
|
1270
1103
|
|
|
1271
1104
|
function publicProjectFlowMarketplaceResource(resource) {
|
|
@@ -1295,6 +1128,20 @@ function sortMarketplaceResources(items) {
|
|
|
1295
1128
|
));
|
|
1296
1129
|
}
|
|
1297
1130
|
|
|
1131
|
+
function paginateMarketplaceResources(items, url, defaultLimit = 24) {
|
|
1132
|
+
const requestedLimit = Number(url.searchParams.get("limit") || defaultLimit);
|
|
1133
|
+
const limit = Math.min(100, Math.max(1, Number.isFinite(requestedLimit) ? Math.floor(requestedLimit) : defaultLimit));
|
|
1134
|
+
const requestedCursor = Number(url.searchParams.get("cursor") || 0);
|
|
1135
|
+
const offset = Math.max(0, Number.isFinite(requestedCursor) ? Math.floor(requestedCursor) : 0);
|
|
1136
|
+
const pageItems = items.slice(offset, offset + limit);
|
|
1137
|
+
const nextOffset = offset + pageItems.length;
|
|
1138
|
+
return {
|
|
1139
|
+
items: pageItems,
|
|
1140
|
+
total: items.length,
|
|
1141
|
+
nextCursor: nextOffset < items.length ? String(nextOffset) : "",
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1298
1145
|
/**
|
|
1299
1146
|
* @param {import('http').IncomingMessage} req
|
|
1300
1147
|
* @param {import('http').ServerResponse} res
|
|
@@ -1357,6 +1204,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1357
1204
|
}
|
|
1358
1205
|
try {
|
|
1359
1206
|
const result = publishNodePackageArchive(root, parsed.file, { ownerUserId: userCtx.userId });
|
|
1207
|
+
if (result.ok) markRepositoryIndexDirty(root);
|
|
1360
1208
|
json(res, result.ok ? (result.alreadyExists ? 200 : 201) : (result.conflict ? 409 : 400), result);
|
|
1361
1209
|
} catch (e) {
|
|
1362
1210
|
json(res, 500, { ok: false, error: (e && e.message) || String(e) });
|
|
@@ -1389,6 +1237,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1389
1237
|
actorUserId: userCtx.userId || "",
|
|
1390
1238
|
eventId: `install:node:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1391
1239
|
});
|
|
1240
|
+
markRepositoryIndexDirty(root);
|
|
1392
1241
|
res.writeHead(200, {
|
|
1393
1242
|
"Content-Type": "application/zip",
|
|
1394
1243
|
"Content-Length": result.archive.length,
|
|
@@ -1412,7 +1261,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1412
1261
|
const marketplaceScope = scope === "owned" ? "owned" : "all";
|
|
1413
1262
|
const queryText = String(url.searchParams.get("q") || "").trim().toLowerCase();
|
|
1414
1263
|
if (kind === "node") {
|
|
1415
|
-
const marketplaceNodes =
|
|
1264
|
+
const marketplaceNodes = listIndexedNodes(root, userCtx, marketplaceScope)
|
|
1416
1265
|
.map((node) => ({ ...node, resourceType: "node", installed: true }));
|
|
1417
1266
|
let nodes = marketplaceNodes;
|
|
1418
1267
|
if (scope === "installed") {
|
|
@@ -1443,14 +1292,23 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1443
1292
|
});
|
|
1444
1293
|
}
|
|
1445
1294
|
nodes = sortMarketplaceResources(nodes.filter((item) => marketplaceResourceMatches(item, queryText)));
|
|
1446
|
-
|
|
1295
|
+
const page = paginateMarketplaceResources(nodes, url);
|
|
1296
|
+
const index = getRepositoryIndex(root);
|
|
1297
|
+
json(res, 200, {
|
|
1298
|
+
kind,
|
|
1299
|
+
scope,
|
|
1300
|
+
sort: "useCount",
|
|
1301
|
+
order: "desc",
|
|
1302
|
+
...page,
|
|
1303
|
+
index: { status: "ready", generatedAt: index.generatedAt },
|
|
1304
|
+
});
|
|
1447
1305
|
return;
|
|
1448
1306
|
}
|
|
1449
1307
|
if (kind !== "flow") {
|
|
1450
1308
|
json(res, 400, { error: "kind must be flow or node" });
|
|
1451
1309
|
return;
|
|
1452
1310
|
}
|
|
1453
|
-
const installedCopies =
|
|
1311
|
+
const installedCopies = indexedMarketplaceFlowCopies(root, userCtx.userId);
|
|
1454
1312
|
const projectFlows = listRunnableProjectMarketplaceFlows(root, userCtx, scope).map((flow) => {
|
|
1455
1313
|
const installedFlowIds = installedCopies.get(`${flow.id}@${flow.version}`) || [];
|
|
1456
1314
|
return publicProjectFlowMarketplaceResource({
|
|
@@ -1464,7 +1322,16 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1464
1322
|
.filter((item) => scope !== "installed" || item.installed)
|
|
1465
1323
|
.filter((item) => marketplaceResourceMatches(item, queryText)),
|
|
1466
1324
|
);
|
|
1467
|
-
|
|
1325
|
+
const page = paginateMarketplaceResources(items, url);
|
|
1326
|
+
const index = getRepositoryIndex(root);
|
|
1327
|
+
json(res, 200, {
|
|
1328
|
+
kind,
|
|
1329
|
+
scope,
|
|
1330
|
+
sort: "useCount",
|
|
1331
|
+
order: "desc",
|
|
1332
|
+
...page,
|
|
1333
|
+
index: { status: "ready", generatedAt: index.generatedAt },
|
|
1334
|
+
});
|
|
1468
1335
|
} catch (e) {
|
|
1469
1336
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1470
1337
|
}
|
|
@@ -1517,7 +1384,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1517
1384
|
return;
|
|
1518
1385
|
}
|
|
1519
1386
|
try {
|
|
1520
|
-
const installedCopies =
|
|
1387
|
+
const installedCopies = indexedMarketplaceFlowCopies(root, userCtx.userId);
|
|
1521
1388
|
if (projectFlow) {
|
|
1522
1389
|
const source = listRunnableProjectMarketplaceFlows(root, userCtx, "all")
|
|
1523
1390
|
.find((flow) => flow.id === id);
|
|
@@ -1529,13 +1396,19 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1529
1396
|
json(res, 409, { error: "该流程已有新版本,请刷新流程仓库后重试" });
|
|
1530
1397
|
return;
|
|
1531
1398
|
}
|
|
1399
|
+
const preview = indexedProjectFlowPreview(root, source);
|
|
1400
|
+
if (!preview || preview.stale) {
|
|
1401
|
+
markRepositoryIndexDirty(root);
|
|
1402
|
+
json(res, preview?.stale ? 409 : 404, { error: preview?.stale ? "该流程已有新版本,请刷新流程仓库后重试" : "Project flow preview is unavailable" });
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1532
1405
|
json(res, 200, {
|
|
1533
1406
|
flow: {
|
|
1534
1407
|
...publicProjectFlowMarketplaceResource(source),
|
|
1535
1408
|
owned: source.ownerUserId === userCtx.userId,
|
|
1536
1409
|
installedFlowIds: installedCopies.get(`${id}@${version}`) || [],
|
|
1537
1410
|
},
|
|
1538
|
-
graph:
|
|
1411
|
+
graph: preview.graph,
|
|
1539
1412
|
});
|
|
1540
1413
|
return;
|
|
1541
1414
|
}
|
|
@@ -1580,11 +1453,11 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1580
1453
|
return;
|
|
1581
1454
|
}
|
|
1582
1455
|
try {
|
|
1583
|
-
const installedCopies =
|
|
1456
|
+
const installedCopies = indexedMarketplaceFlowCopies(root, userCtx.userId);
|
|
1584
1457
|
let graph;
|
|
1585
1458
|
let resource;
|
|
1586
1459
|
if (previewKind === "node") {
|
|
1587
|
-
const node =
|
|
1460
|
+
const node = listIndexedNodes(root, userCtx, "all")
|
|
1588
1461
|
.find((item) => item.id === id && item.version === version);
|
|
1589
1462
|
if (!node) {
|
|
1590
1463
|
json(res, 404, { error: "Node package not found or is private" });
|
|
@@ -1629,7 +1502,13 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1629
1502
|
json(res, 409, { error: "该流程已有新版本,请刷新流程仓库后重试" });
|
|
1630
1503
|
return;
|
|
1631
1504
|
}
|
|
1632
|
-
|
|
1505
|
+
const preview = indexedProjectFlowPreview(root, source);
|
|
1506
|
+
if (!preview || preview.stale) {
|
|
1507
|
+
markRepositoryIndexDirty(root);
|
|
1508
|
+
json(res, preview?.stale ? 409 : 404, { error: preview?.stale ? "该流程已有新版本,请刷新流程仓库后重试" : "Project flow preview is unavailable" });
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
graph = preview.graph;
|
|
1633
1512
|
resource = {
|
|
1634
1513
|
...publicProjectFlowMarketplaceResource(source),
|
|
1635
1514
|
owned: source.ownerUserId === userCtx.userId,
|
|
@@ -1746,6 +1625,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1746
1625
|
visibility: payload?.visibility || "public",
|
|
1747
1626
|
graph,
|
|
1748
1627
|
}, userCtx);
|
|
1628
|
+
if (result.ok) markRepositoryIndexDirty(root);
|
|
1749
1629
|
json(res, result.ok ? (result.alreadyExists ? 200 : 201) : (result.conflict ? 409 : 400), result);
|
|
1750
1630
|
} catch (e) {
|
|
1751
1631
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
@@ -1789,7 +1669,13 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1789
1669
|
json(res, 409, { error: "该流程已有新版本,请刷新流程仓库后重试" });
|
|
1790
1670
|
return;
|
|
1791
1671
|
}
|
|
1792
|
-
|
|
1672
|
+
const preview = indexedProjectFlowPreview(root, source);
|
|
1673
|
+
if (!preview || preview.stale) {
|
|
1674
|
+
markRepositoryIndexDirty(root);
|
|
1675
|
+
json(res, preview?.stale ? 409 : 404, { error: preview?.stale ? "该流程已有新版本,请刷新流程仓库后重试" : "Project flow preview is unavailable" });
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
sourceGraph = preview.graph;
|
|
1793
1679
|
originKind = "project-flow";
|
|
1794
1680
|
} else {
|
|
1795
1681
|
const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
|
|
@@ -1811,6 +1697,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1811
1697
|
actorUserId: userCtx.userId || "",
|
|
1812
1698
|
eventId: `install:${originKind}:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1813
1699
|
});
|
|
1700
|
+
markRepositoryIndexDirty(root);
|
|
1814
1701
|
json(res, 201, {
|
|
1815
1702
|
ok: true,
|
|
1816
1703
|
id,
|
|
@@ -1846,17 +1733,27 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1846
1733
|
return;
|
|
1847
1734
|
}
|
|
1848
1735
|
const metadata = writeProjectFlowMarketplaceMetadata(source._flowRoot, payload?.visibility);
|
|
1736
|
+
updateIndexedProjectFlowVisibility(root, id, metadata.visibility, metadata.updatedAt);
|
|
1849
1737
|
json(res, 200, { ok: true, kind: "project-flow", id, version: source.version, ...metadata });
|
|
1850
1738
|
return;
|
|
1851
1739
|
}
|
|
1740
|
+
const resourceKind = String(payload?.kind || "");
|
|
1741
|
+
const resourceId = String(payload?.id || "");
|
|
1742
|
+
const resourceVersion = String(payload?.version || "");
|
|
1743
|
+
const resourceVisibility = String(payload?.visibility || "public");
|
|
1852
1744
|
const result = setMarketplaceVisibility(
|
|
1853
1745
|
root,
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1746
|
+
resourceKind,
|
|
1747
|
+
resourceId,
|
|
1748
|
+
resourceVersion,
|
|
1749
|
+
resourceVisibility,
|
|
1858
1750
|
userCtx,
|
|
1859
1751
|
);
|
|
1752
|
+
if (result.ok && resourceKind === "node") {
|
|
1753
|
+
updateIndexedNodeVisibility(root, resourceId, resourceVersion, resourceVisibility);
|
|
1754
|
+
} else if (result.ok) {
|
|
1755
|
+
markRepositoryIndexDirty(root);
|
|
1756
|
+
}
|
|
1860
1757
|
json(res, result.ok ? 200 : 400, result);
|
|
1861
1758
|
} catch (e) {
|
|
1862
1759
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
@@ -2391,6 +2288,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2391
2288
|
const graph = workspaceGraphWithScheduleMode(draftGraph, payload.scheduleMode || "disabled");
|
|
2392
2289
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
2393
2290
|
writeWorkspaceGraph(targetDir, graph, root);
|
|
2291
|
+
markRepositoryIndexDirty(root);
|
|
2394
2292
|
let collaborationCreated = false;
|
|
2395
2293
|
if (flowSource === "workspace") {
|
|
2396
2294
|
ensureWorkspaceCollaboration({ flowId, userId: authUser.userId });
|
|
@@ -2674,6 +2572,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2674
2572
|
json(res, result.conflict ? 409 : 400, result);
|
|
2675
2573
|
return;
|
|
2676
2574
|
}
|
|
2575
|
+
markRepositoryIndexDirty(root);
|
|
2677
2576
|
const graph = readWorkspaceGraph(scoped.root, root).graph;
|
|
2678
2577
|
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx);
|
|
2679
2578
|
broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
|
|
@@ -2722,6 +2621,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2722
2621
|
json(res, 404, result);
|
|
2723
2622
|
return;
|
|
2724
2623
|
}
|
|
2624
|
+
markRepositoryIndexDirty(root);
|
|
2725
2625
|
const graph = readWorkspaceGraph(scoped.root, root).graph;
|
|
2726
2626
|
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx);
|
|
2727
2627
|
broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
|
|
@@ -2849,6 +2749,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2849
2749
|
}
|
|
2850
2750
|
throw new Error(`Workspace graph save rolled back: ${(scheduleError && scheduleError.message) || String(scheduleError)}`);
|
|
2851
2751
|
}
|
|
2752
|
+
markRepositoryIndexDirty(root);
|
|
2852
2753
|
broadcastWorkspaceCollaborationEvent(
|
|
2853
2754
|
userCtx,
|
|
2854
2755
|
scoped.flowSource,
|
|
@@ -2960,6 +2861,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2960
2861
|
graph.instances[scheduleNodeId] = { ...instance, body: JSON.stringify(config) };
|
|
2961
2862
|
const committed = commitWorkspaceGraph(root, scoped, graph, userCtx);
|
|
2962
2863
|
const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, committed.graph, authUser, userCtx);
|
|
2864
|
+
markRepositoryIndexDirty(root);
|
|
2963
2865
|
const schedule = workspaceSchedules.find((item) => item.scheduleNodeId === scheduleNodeId) || {
|
|
2964
2866
|
flowId,
|
|
2965
2867
|
flowSource,
|
|
@@ -4032,6 +3934,436 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
4032
3934
|
return;
|
|
4033
3935
|
}
|
|
4034
3936
|
|
|
3937
|
+
if (req.method === "GET" && url.pathname === "/api/workspace/explorations") {
|
|
3938
|
+
try {
|
|
3939
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3940
|
+
flowId: url.searchParams.get("flowId") || "",
|
|
3941
|
+
flowSource: url.searchParams.get("flowSource") || "user",
|
|
3942
|
+
adminOwnerId: url.searchParams.get("adminOwnerId") || "",
|
|
3943
|
+
archived: url.searchParams.get("archived") === "1" || url.searchParams.get("flowArchived") === "1",
|
|
3944
|
+
}, userCtx);
|
|
3945
|
+
if (scoped.error) {
|
|
3946
|
+
json(res, 400, { error: scoped.error });
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
json(res, 200, { ok: true, explorations: listAiExplorationSessions(scoped.root) });
|
|
3950
|
+
} catch (e) {
|
|
3951
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
3952
|
+
}
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
if (req.method === "GET" && url.pathname === "/api/workspace/exploration") {
|
|
3957
|
+
try {
|
|
3958
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3959
|
+
flowId: url.searchParams.get("flowId") || "",
|
|
3960
|
+
flowSource: url.searchParams.get("flowSource") || "user",
|
|
3961
|
+
adminOwnerId: url.searchParams.get("adminOwnerId") || "",
|
|
3962
|
+
archived: url.searchParams.get("archived") === "1" || url.searchParams.get("flowArchived") === "1",
|
|
3963
|
+
}, userCtx);
|
|
3964
|
+
if (scoped.error) {
|
|
3965
|
+
json(res, 400, { error: scoped.error });
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
3968
|
+
json(res, 200, { ok: true, exploration: readAiExplorationSession(scoped.root, url.searchParams.get("id") || "") });
|
|
3969
|
+
} catch (e) {
|
|
3970
|
+
json(res, /Invalid exploration|ENOENT/.test(String(e?.message || e)) ? 404 : 500, { error: (e && e.message) || String(e) });
|
|
3971
|
+
}
|
|
3972
|
+
return;
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/exploration") {
|
|
3976
|
+
let payload;
|
|
3977
|
+
try { payload = JSON.parse(await readBody(req)); } catch {
|
|
3978
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
3979
|
+
return;
|
|
3980
|
+
}
|
|
3981
|
+
try {
|
|
3982
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3983
|
+
flowId: payload.flowId || "",
|
|
3984
|
+
flowSource: payload.flowSource || "user",
|
|
3985
|
+
adminOwnerId: payload.adminOwnerId || "",
|
|
3986
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
3987
|
+
}, userCtx);
|
|
3988
|
+
if (scoped.error) {
|
|
3989
|
+
json(res, 400, { error: scoped.error });
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource) || scoped.collaborationAccess?.writable === false) {
|
|
3993
|
+
json(res, 403, { error: "Exploration write permission denied" });
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
const exploration = createAiExplorationSession(scoped.root, {
|
|
3997
|
+
title: payload.title,
|
|
3998
|
+
goal: payload.goal,
|
|
3999
|
+
mode: payload.mode || "observed",
|
|
4000
|
+
status: payload.status || "running",
|
|
4001
|
+
source: payload.source || { provider: "external", agent: "custom" },
|
|
4002
|
+
});
|
|
4003
|
+
json(res, 201, {
|
|
4004
|
+
ok: true,
|
|
4005
|
+
exploration,
|
|
4006
|
+
ingest: {
|
|
4007
|
+
method: "POST",
|
|
4008
|
+
path: "/api/workspace/exploration/events",
|
|
4009
|
+
body: {
|
|
4010
|
+
flowId: payload.flowId || "",
|
|
4011
|
+
flowSource: payload.flowSource || "user",
|
|
4012
|
+
id: exploration.id,
|
|
4013
|
+
events: [],
|
|
4014
|
+
},
|
|
4015
|
+
},
|
|
4016
|
+
});
|
|
4017
|
+
} catch (e) {
|
|
4018
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
4019
|
+
}
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/exploration/events") {
|
|
4024
|
+
let payload;
|
|
4025
|
+
try { payload = JSON.parse(await readBody(req)); } catch {
|
|
4026
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
4029
|
+
try {
|
|
4030
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
4031
|
+
flowId: payload.flowId || "",
|
|
4032
|
+
flowSource: payload.flowSource || "user",
|
|
4033
|
+
adminOwnerId: payload.adminOwnerId || "",
|
|
4034
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
4035
|
+
}, userCtx);
|
|
4036
|
+
if (scoped.error) {
|
|
4037
|
+
json(res, 400, { error: scoped.error });
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource) || scoped.collaborationAccess?.writable === false) {
|
|
4041
|
+
json(res, 403, { error: "Exploration write permission denied" });
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
const appended = appendAiTraceEvents(scoped.root, payload.id, payload.events || [], {
|
|
4045
|
+
phase: payload.phase || "observed",
|
|
4046
|
+
});
|
|
4047
|
+
if (payload.status || payload.summary) {
|
|
4048
|
+
appended.session = updateAiExplorationSession(scoped.root, payload.id, {
|
|
4049
|
+
...(payload.status ? { status: payload.status } : {}),
|
|
4050
|
+
...(payload.summary ? { summary: payload.summary } : {}),
|
|
4051
|
+
});
|
|
4052
|
+
}
|
|
4053
|
+
json(res, 200, { ok: true, ...appended });
|
|
4054
|
+
} catch (e) {
|
|
4055
|
+
json(res, /Invalid exploration|ENOENT/.test(String(e?.message || e)) ? 404 : 400, { error: (e && e.message) || String(e) });
|
|
4056
|
+
}
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/exploration/plan") {
|
|
4061
|
+
let payload;
|
|
4062
|
+
try { payload = JSON.parse(await readBody(req)); } catch {
|
|
4063
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
const goal = String(payload?.goal || "").trim();
|
|
4067
|
+
if (!goal) {
|
|
4068
|
+
json(res, 400, { error: "Missing exploration goal" });
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
let scoped;
|
|
4072
|
+
let exploration;
|
|
4073
|
+
try {
|
|
4074
|
+
scoped = resolveWorkspaceScopeRoot(root, {
|
|
4075
|
+
flowId: payload.flowId || "",
|
|
4076
|
+
flowSource: payload.flowSource || "user",
|
|
4077
|
+
adminOwnerId: payload.adminOwnerId || "",
|
|
4078
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
4079
|
+
}, userCtx);
|
|
4080
|
+
if (scoped.error) {
|
|
4081
|
+
json(res, 400, { error: scoped.error });
|
|
4082
|
+
return;
|
|
4083
|
+
}
|
|
4084
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource) || scoped.collaborationAccess?.writable === false) {
|
|
4085
|
+
json(res, 403, { error: "Exploration write permission denied" });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
const graphRead = readWorkspaceGraph(scoped.root);
|
|
4089
|
+
const graph = graphRead?.graph || graphRead;
|
|
4090
|
+
const workspaceSource = workspaceGraphAsSource(graph);
|
|
4091
|
+
exploration = createAiExplorationSession(scoped.root, {
|
|
4092
|
+
title: payload.title || goal.slice(0, 80),
|
|
4093
|
+
goal,
|
|
4094
|
+
mode: "planned",
|
|
4095
|
+
status: "planning",
|
|
4096
|
+
source: { provider: "agentflow", agent: String(payload.model || "default") },
|
|
4097
|
+
});
|
|
4098
|
+
const chunks = [];
|
|
4099
|
+
let result = "";
|
|
4100
|
+
const handle = startComposerAgent({
|
|
4101
|
+
uiWorkspaceRoot: scoped.root,
|
|
4102
|
+
cliWorkspace: scoped.root,
|
|
4103
|
+
prompt: aiPlanPrompt({ goal, workspaceSource }),
|
|
4104
|
+
modelKey: typeof payload.model === "string" ? payload.model.trim() : "",
|
|
4105
|
+
agentflowUserId: userCtx.userId || "",
|
|
4106
|
+
mode: "plan",
|
|
4107
|
+
force: false,
|
|
4108
|
+
sandboxDisabled: false,
|
|
4109
|
+
approveMcps: false,
|
|
4110
|
+
sandboxMode: "read-only",
|
|
4111
|
+
allowDanger: false,
|
|
4112
|
+
includeJsonResult: true,
|
|
4113
|
+
onStreamEvent(ev) {
|
|
4114
|
+
if (ev?.type !== "natural" || typeof ev.text !== "string") return;
|
|
4115
|
+
if (ev.kind === "result") result = ev.text.trim();
|
|
4116
|
+
else if (ev.kind === "assistant" && ev.text.trim()) chunks.push(ev.text.trim());
|
|
4117
|
+
},
|
|
4118
|
+
});
|
|
4119
|
+
await handle.finished;
|
|
4120
|
+
const plan = parseAiPlanResult(result || chunks.at(-1) || chunks.join("\n"), exploration.id);
|
|
4121
|
+
const appended = appendAiTraceEvents(scoped.root, exploration.id, plan.events, { phase: "planned" });
|
|
4122
|
+
const ready = updateAiExplorationSession(scoped.root, exploration.id, {
|
|
4123
|
+
title: plan.title,
|
|
4124
|
+
summary: plan.summary,
|
|
4125
|
+
status: "ready",
|
|
4126
|
+
eventCount: appended.session.eventCount,
|
|
4127
|
+
});
|
|
4128
|
+
json(res, 201, { ok: true, exploration: { ...ready, events: appended.events } });
|
|
4129
|
+
} catch (e) {
|
|
4130
|
+
if (scoped?.root && exploration?.id) {
|
|
4131
|
+
try { updateAiExplorationSession(scoped.root, exploration.id, { status: "failed", summary: String(e?.message || e) }); } catch {}
|
|
4132
|
+
}
|
|
4133
|
+
json(res, 500, { error: (e && e.message) || String(e), ...(exploration?.id ? { explorationId: exploration.id } : {}) });
|
|
4134
|
+
}
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/exploration/dry-run") {
|
|
4139
|
+
let payload;
|
|
4140
|
+
try { payload = JSON.parse(await readBody(req)); } catch {
|
|
4141
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
4144
|
+
try {
|
|
4145
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
4146
|
+
flowId: payload.flowId || "",
|
|
4147
|
+
flowSource: payload.flowSource || "user",
|
|
4148
|
+
adminOwnerId: payload.adminOwnerId || "",
|
|
4149
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
4150
|
+
}, userCtx);
|
|
4151
|
+
if (scoped.error) {
|
|
4152
|
+
json(res, 400, { error: scoped.error });
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource) || scoped.collaborationAccess?.writable === false) {
|
|
4156
|
+
json(res, 403, { error: "Exploration dry-run permission denied" });
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
const exploration = readAiExplorationSession(scoped.root, payload.id);
|
|
4160
|
+
const planned = exploration.events.filter((event) => event.phase === "planned");
|
|
4161
|
+
if (!planned.length) {
|
|
4162
|
+
json(res, 400, { error: "Exploration has no planned spans" });
|
|
4163
|
+
return;
|
|
4164
|
+
}
|
|
4165
|
+
const runToken = Date.now().toString(36);
|
|
4166
|
+
const simulated = planned.map((event, index) => {
|
|
4167
|
+
const blocked = event.requiresApproval === true || ["write", "external"].includes(event.sideEffect);
|
|
4168
|
+
return {
|
|
4169
|
+
id: `dry_${runToken}_${index + 1}`,
|
|
4170
|
+
spanId: `dry_${runToken}_${event.spanId}`,
|
|
4171
|
+
parentSpanId: event.parentSpanId ? `dry_${runToken}_${event.parentSpanId}` : "",
|
|
4172
|
+
phase: "simulated",
|
|
4173
|
+
type: event.type,
|
|
4174
|
+
name: event.name,
|
|
4175
|
+
summary: blocked
|
|
4176
|
+
? `策略预检已阻断:${event.summary || event.name}。真实执行前需要明确授权。`
|
|
4177
|
+
: `策略预检通过:${event.summary || event.name}。本次未执行真实工具。`,
|
|
4178
|
+
sideEffect: event.sideEffect,
|
|
4179
|
+
requiresApproval: blocked,
|
|
4180
|
+
status: blocked ? "blocked" : "success",
|
|
4181
|
+
inputPreview: event.inputPreview || "",
|
|
4182
|
+
outputPreview: blocked ? "副作用未执行" : "约束检查通过,未执行真实工具",
|
|
4183
|
+
};
|
|
4184
|
+
});
|
|
4185
|
+
const appended = appendAiTraceEvents(scoped.root, exploration.id, simulated, { phase: "simulated" });
|
|
4186
|
+
const blockedCount = appended.events.filter((event) => event.status === "blocked").length;
|
|
4187
|
+
const updated = updateAiExplorationSession(scoped.root, exploration.id, {
|
|
4188
|
+
mode: "simulated",
|
|
4189
|
+
status: "ready",
|
|
4190
|
+
summary: `Dry-run 策略预检完成:${appended.events.length - blockedCount} 项通过,${blockedCount} 项等待授权。`,
|
|
4191
|
+
eventCount: appended.session.eventCount,
|
|
4192
|
+
});
|
|
4193
|
+
json(res, 200, {
|
|
4194
|
+
ok: true,
|
|
4195
|
+
exploration: { ...updated, events: [...exploration.events, ...appended.events] },
|
|
4196
|
+
dryRun: { kind: "policy-check", executedTools: false, blockedCount },
|
|
4197
|
+
});
|
|
4198
|
+
} catch (e) {
|
|
4199
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
4200
|
+
}
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/exploration/materialize") {
|
|
4205
|
+
let payload;
|
|
4206
|
+
try { payload = JSON.parse(await readBody(req)); } catch {
|
|
4207
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
4210
|
+
let scoped;
|
|
4211
|
+
let exploration;
|
|
4212
|
+
let materializeRunId = "";
|
|
4213
|
+
try {
|
|
4214
|
+
scoped = resolveWorkspaceScopeRoot(root, {
|
|
4215
|
+
flowId: payload.flowId || "",
|
|
4216
|
+
flowSource: payload.flowSource || "user",
|
|
4217
|
+
adminOwnerId: payload.adminOwnerId || "",
|
|
4218
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
4219
|
+
}, userCtx);
|
|
4220
|
+
if (scoped.error) {
|
|
4221
|
+
json(res, 400, { error: scoped.error });
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource) || scoped.collaborationAccess?.writable === false) {
|
|
4225
|
+
json(res, 403, { error: "Exploration materialization permission denied" });
|
|
4226
|
+
return;
|
|
4227
|
+
}
|
|
4228
|
+
exploration = readAiExplorationSession(scoped.root, payload.id);
|
|
4229
|
+
if (!exploration.events.length) {
|
|
4230
|
+
json(res, 400, { error: "Exploration has no trace events" });
|
|
4231
|
+
return;
|
|
4232
|
+
}
|
|
4233
|
+
const materializableEvents = materializableAiTraceEvents(exploration);
|
|
4234
|
+
if (!materializableEvents.length) {
|
|
4235
|
+
json(res, 400, { error: "Exploration has no planned or observed spans to materialize" });
|
|
4236
|
+
return;
|
|
4237
|
+
}
|
|
4238
|
+
const dangerousEvents = materializableEvents.filter((event) => event.requiresApproval || ["write", "external"].includes(event.sideEffect));
|
|
4239
|
+
if (dangerousEvents.length && payload.approveSideEffects !== true) {
|
|
4240
|
+
json(res, 409, {
|
|
4241
|
+
error: "Side-effect review is required before materialization",
|
|
4242
|
+
sideEffects: dangerousEvents.map((event) => ({ spanId: event.spanId, name: event.name, sideEffect: event.sideEffect })),
|
|
4243
|
+
});
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
const beforeGraphRead = readWorkspaceGraph(scoped.root);
|
|
4247
|
+
const beforeGraph = beforeGraphRead?.graph || beforeGraphRead;
|
|
4248
|
+
const beforeIds = new Set([
|
|
4249
|
+
...(beforeGraph.nodes || []).map((node) => String(node.id || "")),
|
|
4250
|
+
...Object.keys(beforeGraph.instances || {}),
|
|
4251
|
+
].filter(Boolean));
|
|
4252
|
+
materializeRunId = `materialize_${Date.now().toString(36)}`;
|
|
4253
|
+
appendAiTraceEvents(scoped.root, exploration.id, [{
|
|
4254
|
+
id: `${materializeRunId}_start`,
|
|
4255
|
+
spanId: materializeRunId,
|
|
4256
|
+
type: "agent",
|
|
4257
|
+
name: "固化 Agent",
|
|
4258
|
+
summary: "开始把已审核 Trace 转换为 Workspace DSL 调整态",
|
|
4259
|
+
status: "running",
|
|
4260
|
+
phase: "observed",
|
|
4261
|
+
sideEffect: "write",
|
|
4262
|
+
}], { phase: "observed" });
|
|
4263
|
+
updateAiExplorationSession(scoped.root, exploration.id, { mode: "observed", status: "running" });
|
|
4264
|
+
let reply = "";
|
|
4265
|
+
let toolIndex = 0;
|
|
4266
|
+
const handle = startComposerAgent({
|
|
4267
|
+
uiWorkspaceRoot: scoped.root,
|
|
4268
|
+
cliWorkspace: scoped.root,
|
|
4269
|
+
prompt: aiMaterializationPrompt(exploration, workspaceGraphAsSource(beforeGraph)),
|
|
4270
|
+
modelKey: typeof payload.model === "string" ? payload.model.trim() : "",
|
|
4271
|
+
agentflowUserId: userCtx.userId || "",
|
|
4272
|
+
force: true,
|
|
4273
|
+
sandboxDisabled: false,
|
|
4274
|
+
approveMcps: false,
|
|
4275
|
+
sandboxMode: "workspace-write",
|
|
4276
|
+
allowDanger: false,
|
|
4277
|
+
onToolCall(subtype, toolName) {
|
|
4278
|
+
toolIndex += 1;
|
|
4279
|
+
try {
|
|
4280
|
+
const toolStatus = /(?:complete|success|done)/i.test(String(subtype || ""))
|
|
4281
|
+
? "success"
|
|
4282
|
+
: /(?:error|fail)/i.test(String(subtype || "")) ? "error" : "running";
|
|
4283
|
+
const sideEffect = classifyAiToolSideEffect(toolName, subtype);
|
|
4284
|
+
appendAiTraceEvents(scoped.root, exploration.id, [{
|
|
4285
|
+
id: `${materializeRunId}_tool_${toolIndex}`,
|
|
4286
|
+
spanId: `${materializeRunId}_tool_${toolIndex}`,
|
|
4287
|
+
parentSpanId: materializeRunId,
|
|
4288
|
+
type: String(subtype || "").toLowerCase() === "thinking" ? "decision" : "tool",
|
|
4289
|
+
name: String(toolName || subtype || "Agent tool"),
|
|
4290
|
+
summary: subtype ? `Agent 工具事件:${subtype}` : "Agent 工具调用",
|
|
4291
|
+
status: toolStatus,
|
|
4292
|
+
phase: "observed",
|
|
4293
|
+
sideEffect,
|
|
4294
|
+
}], { phase: "observed" });
|
|
4295
|
+
} catch {
|
|
4296
|
+
// Trace persistence must not interrupt an in-flight materialization.
|
|
4297
|
+
}
|
|
4298
|
+
},
|
|
4299
|
+
onStreamEvent(ev) {
|
|
4300
|
+
if (ev?.type === "natural" && ["assistant", "result"].includes(ev.kind) && typeof ev.text === "string" && ev.text.trim()) {
|
|
4301
|
+
reply = ev.text.trim();
|
|
4302
|
+
}
|
|
4303
|
+
},
|
|
4304
|
+
});
|
|
4305
|
+
await handle.finished;
|
|
4306
|
+
appendAiTraceEvents(scoped.root, exploration.id, [{
|
|
4307
|
+
id: `${materializeRunId}_finish`,
|
|
4308
|
+
spanId: materializeRunId,
|
|
4309
|
+
type: "agent",
|
|
4310
|
+
name: "固化 Agent",
|
|
4311
|
+
summary: reply || "Workspace DSL 调整态生成完成",
|
|
4312
|
+
status: "success",
|
|
4313
|
+
phase: "observed",
|
|
4314
|
+
sideEffect: "write",
|
|
4315
|
+
endedAt: new Date().toISOString(),
|
|
4316
|
+
}], { phase: "observed" });
|
|
4317
|
+
const graphRead = readWorkspaceGraph(scoped.root);
|
|
4318
|
+
const graph = graphRead?.graph || graphRead;
|
|
4319
|
+
const graphIds = [
|
|
4320
|
+
...(graph.nodes || []).map((node) => String(node.id || "")),
|
|
4321
|
+
...Object.keys(graph.instances || {}),
|
|
4322
|
+
].filter(Boolean);
|
|
4323
|
+
const nodeIds = [...new Set(graphIds)].filter((id) => !beforeIds.has(id));
|
|
4324
|
+
const materialization = writeAiExplorationMaterialization(scoped.root, exploration.id, {
|
|
4325
|
+
nodeIds,
|
|
4326
|
+
designRevision: workspaceDesignRevision(graph),
|
|
4327
|
+
});
|
|
4328
|
+
appendAiTraceEvents(scoped.root, exploration.id, [{
|
|
4329
|
+
type: "status",
|
|
4330
|
+
name: "固化为 Workspace DSL",
|
|
4331
|
+
summary: reply || `新增 ${nodeIds.length} 个节点`,
|
|
4332
|
+
status: "success",
|
|
4333
|
+
phase: "materialized",
|
|
4334
|
+
sideEffect: "write",
|
|
4335
|
+
artifacts: [{ kind: "dsl", path: "workspace.flow.js", label: "Workspace DSL" }],
|
|
4336
|
+
}], { phase: "materialized" });
|
|
4337
|
+
const updated = updateAiExplorationSession(scoped.root, exploration.id, {
|
|
4338
|
+
mode: "materialized",
|
|
4339
|
+
status: "completed",
|
|
4340
|
+
materializedAt: materialization.materializedAt,
|
|
4341
|
+
});
|
|
4342
|
+
json(res, 200, { ok: true, exploration: updated, materialization, graph, reply });
|
|
4343
|
+
} catch (e) {
|
|
4344
|
+
if (scoped?.root && exploration?.id) {
|
|
4345
|
+
try {
|
|
4346
|
+
if (materializeRunId) {
|
|
4347
|
+
appendAiTraceEvents(scoped.root, exploration.id, [{
|
|
4348
|
+
id: `${materializeRunId}_error`,
|
|
4349
|
+
spanId: materializeRunId,
|
|
4350
|
+
type: "agent",
|
|
4351
|
+
name: "固化 Agent",
|
|
4352
|
+
summary: String(e?.message || e),
|
|
4353
|
+
status: "error",
|
|
4354
|
+
phase: "observed",
|
|
4355
|
+
sideEffect: "write",
|
|
4356
|
+
endedAt: new Date().toISOString(),
|
|
4357
|
+
}], { phase: "observed" });
|
|
4358
|
+
}
|
|
4359
|
+
updateAiExplorationSession(scoped.root, exploration.id, { mode: "observed", status: "failed" });
|
|
4360
|
+
} catch {}
|
|
4361
|
+
}
|
|
4362
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
4363
|
+
}
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4035
4367
|
if (url.pathname === "/api/workspace/conversations") {
|
|
4036
4368
|
let payload = {};
|
|
4037
4369
|
if (req.method === "POST") {
|
|
@@ -4478,6 +4810,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
4478
4810
|
json(res, result.conflict ? 409 : 400, { error: result.error || "发布失败" });
|
|
4479
4811
|
return;
|
|
4480
4812
|
}
|
|
4813
|
+
markRepositoryIndexDirty(root);
|
|
4481
4814
|
json(res, 200, { ok: true, ...result });
|
|
4482
4815
|
} catch (e) {
|
|
4483
4816
|
json(res, 500, { error: (e && e.message) || String(e) });
|