@fieldwangai/agentflow 0.1.105 → 0.1.107
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 +5 -0
- package/bin/lib/composer-agent.mjs +3 -0
- package/bin/lib/prd-workflow-collaboration.mjs +69 -0
- package/bin/lib/ui-server.mjs +612 -52
- package/bin/lib/workflow-report.mjs +431 -0
- package/builtin/web-ui/dist/assets/index-BZMWrBdo.css +1 -0
- package/builtin/web-ui/dist/assets/index-Ds1YW25U.js +348 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-cli/SKILL.md +72 -1
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +83 -0
- package/builtin/web-ui/dist/assets/index-CC1ceU6X.css +0 -1
- package/builtin/web-ui/dist/assets/index-DuSU9kCW.js +0 -348
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -152,11 +152,24 @@ import {
|
|
|
152
152
|
addPrdWorkflowCollaborationMember,
|
|
153
153
|
ensurePrdWorkflowCollaboration,
|
|
154
154
|
getPrdWorkflowCollaborationById,
|
|
155
|
+
getPrdWorkflowCollaborationByShareToken,
|
|
155
156
|
getPrdWorkflowCollaborationForUser,
|
|
157
|
+
ensurePrdWorkflowShareLink,
|
|
156
158
|
prdWorkflowCollaborationAccess,
|
|
157
159
|
prdWorkflowCollaborationSummary,
|
|
158
160
|
removePrdWorkflowCollaborationMember,
|
|
161
|
+
revokePrdWorkflowShareLink,
|
|
159
162
|
} from "./prd-workflow-collaboration.mjs";
|
|
163
|
+
import {
|
|
164
|
+
legacyOverallToGlobalState,
|
|
165
|
+
materializeWorkflowGlobalState,
|
|
166
|
+
mergeWorkflowArtifactLists,
|
|
167
|
+
mergeWorkflowArtifacts,
|
|
168
|
+
mergeWorkflowGlobalState,
|
|
169
|
+
normalizeWorkflowReference,
|
|
170
|
+
normalizeWorkflowReport,
|
|
171
|
+
workflowRuntimeRevision,
|
|
172
|
+
} from "./workflow-report.mjs";
|
|
160
173
|
|
|
161
174
|
const MIME = {
|
|
162
175
|
".html": "text/html; charset=utf-8",
|
|
@@ -173,6 +186,11 @@ const MIME = {
|
|
|
173
186
|
".svg": "image/svg+xml",
|
|
174
187
|
".ico": "image/x-icon",
|
|
175
188
|
};
|
|
189
|
+
const ADMIN_ONLY_USER_ENV_KEYS = new Set([
|
|
190
|
+
"CURSOR_API_KEYS",
|
|
191
|
+
"AGENTFLOW_CURSOR_API_KEY_COOLDOWN_MINUTES",
|
|
192
|
+
"CURSOR_API_KEY_COOLDOWN_MINUTES",
|
|
193
|
+
]);
|
|
176
194
|
|
|
177
195
|
const UI_SERVER_STARTED_AT = new Date().toISOString();
|
|
178
196
|
const UI_SERVER_APP_VERSION = (() => {
|
|
@@ -3227,6 +3245,25 @@ function prdWorkflowCollaborationSummaryWithUsers(record, userId) {
|
|
|
3227
3245
|
};
|
|
3228
3246
|
}
|
|
3229
3247
|
|
|
3248
|
+
function prdWorkflowShareLinkSummary(record, shareToken, publicBaseUrl, userId = "") {
|
|
3249
|
+
const token = String(shareToken || record?.shareToken || "").trim();
|
|
3250
|
+
if (!record || !token) return null;
|
|
3251
|
+
const query = new URLSearchParams({
|
|
3252
|
+
view: "workflow",
|
|
3253
|
+
tapdId: String(record.tapdId || ""),
|
|
3254
|
+
workflowShare: token,
|
|
3255
|
+
});
|
|
3256
|
+
const base = String(publicBaseUrl || "").replace(/\/+$/, "");
|
|
3257
|
+
return {
|
|
3258
|
+
tapdId: String(record.tapdId || ""),
|
|
3259
|
+
url: `${base}/workspace?${query.toString()}`,
|
|
3260
|
+
active: true,
|
|
3261
|
+
readOnly: true,
|
|
3262
|
+
createdAt: record.shareCreatedAt || "",
|
|
3263
|
+
canManage: String(record.ownerId || "") === String(userId || "").trim().toLowerCase(),
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3230
3267
|
function workspaceConversationsPath(scopedRoot) {
|
|
3231
3268
|
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
|
|
3232
3269
|
}
|
|
@@ -4065,9 +4102,50 @@ function workspaceSafeNodeOutputRelPath(value) {
|
|
|
4065
4102
|
return clean;
|
|
4066
4103
|
}
|
|
4067
4104
|
|
|
4068
|
-
function
|
|
4069
|
-
const
|
|
4070
|
-
if (!
|
|
4105
|
+
function workspaceResolveOutputChild(rootDir, relativePath = "") {
|
|
4106
|
+
const root = path.resolve(rootDir || "");
|
|
4107
|
+
if (!rootDir || !root) return "";
|
|
4108
|
+
const parts = String(relativePath || "").split("/").filter(Boolean);
|
|
4109
|
+
const abs = path.resolve(root, ...parts);
|
|
4110
|
+
const rootWithSep = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
4111
|
+
return abs === root || abs.startsWith(rootWithSep) ? abs : "";
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
function workspaceNodeOutputSuffix(relPath) {
|
|
4115
|
+
const clean = workspaceSafeNodeOutputRelPath(relPath);
|
|
4116
|
+
return clean ? clean.slice("outputs/".length).replace(/^\/+/, "") : "";
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
function workspaceNodeOutputWritePath(runPackage, relPath) {
|
|
4120
|
+
const clean = workspaceSafeNodeOutputRelPath(relPath);
|
|
4121
|
+
if (!clean) return "";
|
|
4122
|
+
const suffix = workspaceNodeOutputSuffix(clean);
|
|
4123
|
+
if (runPackage?.directWorkspaceOutputs && runPackage?.outputsDir) {
|
|
4124
|
+
return workspaceResolveOutputChild(runPackage.outputsDir, suffix);
|
|
4125
|
+
}
|
|
4126
|
+
return workspaceResolveOutputChild(runPackage?.nodeRunDir, clean);
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
function workspaceNodeOutputCandidates(runPackage, relPath) {
|
|
4130
|
+
const clean = workspaceSafeNodeOutputRelPath(relPath);
|
|
4131
|
+
if (!clean) return [];
|
|
4132
|
+
const suffix = workspaceNodeOutputSuffix(clean);
|
|
4133
|
+
const direct = runPackage?.outputsDir
|
|
4134
|
+
? workspaceResolveOutputChild(runPackage.outputsDir, suffix)
|
|
4135
|
+
: "";
|
|
4136
|
+
const legacy = runPackage?.nodeRunDir
|
|
4137
|
+
? workspaceResolveOutputChild(runPackage.nodeRunDir, clean)
|
|
4138
|
+
: "";
|
|
4139
|
+
const ordered = runPackage?.directWorkspaceOutputs ? [direct, legacy] : [legacy, direct];
|
|
4140
|
+
return ordered.filter((candidate, index, list) => candidate && list.indexOf(candidate) === index);
|
|
4141
|
+
}
|
|
4142
|
+
|
|
4143
|
+
function workspaceDescribeNodeOutputsDir(runPackage, maxEntries = 30) {
|
|
4144
|
+
const outputDirs = [
|
|
4145
|
+
runPackage?.outputsDir,
|
|
4146
|
+
runPackage?.nodeRunDir ? path.resolve(runPackage.nodeRunDir, "outputs") : "",
|
|
4147
|
+
].filter((dir, index, list) => dir && list.indexOf(dir) === index && fs.existsSync(dir));
|
|
4148
|
+
if (!outputDirs.length) return "Current node outputs directory is missing.";
|
|
4071
4149
|
const entries = [];
|
|
4072
4150
|
const walk = (dir, rel = "") => {
|
|
4073
4151
|
if (entries.length >= maxEntries) return;
|
|
@@ -4084,7 +4162,7 @@ function workspaceDescribeNodeOutputsDir(nodeRunDir, maxEntries = 30) {
|
|
|
4084
4162
|
if (child.isDirectory()) walk(path.join(dir, child.name), childRel);
|
|
4085
4163
|
}
|
|
4086
4164
|
};
|
|
4087
|
-
walk(
|
|
4165
|
+
for (const outputDir of outputDirs) walk(outputDir);
|
|
4088
4166
|
if (!entries.length) return "Current node outputs directory is empty.";
|
|
4089
4167
|
const suffix = entries.length >= maxEntries ? `\n...showing first ${maxEntries} entries` : "";
|
|
4090
4168
|
return `Current node outputs entries:\n${entries.map((entry) => `- outputs/${entry}`).join("\n")}${suffix}`;
|
|
@@ -4097,29 +4175,31 @@ function workspacePublishNodeOutputFile(runPackage, relPath) {
|
|
|
4097
4175
|
const nodeRunDir = path.resolve(runPackage?.nodeRunDir || "");
|
|
4098
4176
|
const workspaceOutputsDir = path.resolve(runPackage?.workspaceOutputsDir || "");
|
|
4099
4177
|
if (!nodeRunDir || !workspaceOutputsDir) return clean;
|
|
4100
|
-
const src =
|
|
4101
|
-
|
|
4102
|
-
if (
|
|
4103
|
-
throw new Error(`Invalid node output path: ${clean}`);
|
|
4104
|
-
}
|
|
4105
|
-
if (!fs.existsSync(src) || !fs.statSync(src).isFile()) {
|
|
4178
|
+
const src = workspaceNodeOutputCandidates(runPackage, clean)
|
|
4179
|
+
.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
4180
|
+
if (!src) {
|
|
4106
4181
|
throw new Error(
|
|
4107
4182
|
`Agent returned resultFile but did not create it under this node's outputs: ${clean}\n` +
|
|
4108
|
-
`Expected file: ${
|
|
4109
|
-
`${workspaceDescribeNodeOutputsDir(
|
|
4110
|
-
`Write
|
|
4183
|
+
`Expected file: ${workspaceNodeOutputWritePath(runPackage, clean)}\n` +
|
|
4184
|
+
`${workspaceDescribeNodeOutputsDir(runPackage)}\n` +
|
|
4185
|
+
`Write downloadable files to the absolute AGENTFLOW_OUTPUTS_DIR path.`
|
|
4111
4186
|
);
|
|
4112
4187
|
}
|
|
4113
4188
|
const nodePart = workspaceSanitizeTmpSegment(runPackage?.nodeId || "node", "node");
|
|
4114
|
-
const destRel = clean
|
|
4115
|
-
const
|
|
4116
|
-
const
|
|
4189
|
+
const destRel = workspaceNodeOutputSuffix(clean);
|
|
4190
|
+
const publishedBase = String(runPackage?.outputsRel || "").trim() || path.posix.join("outputs", nodePart);
|
|
4191
|
+
const publishedRel = path.posix.join(publishedBase, ...destRel.split("/").filter(Boolean));
|
|
4192
|
+
const dest = runPackage?.directWorkspaceOutputs && runPackage?.outputsDir
|
|
4193
|
+
? workspaceResolveOutputChild(runPackage.outputsDir, destRel)
|
|
4194
|
+
: path.resolve(workspaceOutputsDir, nodePart, ...destRel.split("/").filter(Boolean));
|
|
4117
4195
|
const workspaceOutputsWithSep = workspaceOutputsDir.endsWith(path.sep) ? workspaceOutputsDir : `${workspaceOutputsDir}${path.sep}`;
|
|
4118
4196
|
if (dest !== workspaceOutputsDir && !dest.startsWith(workspaceOutputsWithSep)) {
|
|
4119
4197
|
throw new Error(`Invalid workspace output path: ${clean}`);
|
|
4120
4198
|
}
|
|
4121
|
-
|
|
4122
|
-
|
|
4199
|
+
if (src !== dest) {
|
|
4200
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
4201
|
+
fs.copyFileSync(src, dest);
|
|
4202
|
+
}
|
|
4123
4203
|
return publishedRel;
|
|
4124
4204
|
}
|
|
4125
4205
|
|
|
@@ -4130,16 +4210,15 @@ export function workspaceMaterializeAgentResultFile(structured, runPackage) {
|
|
|
4130
4210
|
const declared = workspaceSafeNodeOutputRelPath(rawDeclared);
|
|
4131
4211
|
if (rawDeclared && !declared) return structured;
|
|
4132
4212
|
const relPath = declared || configured;
|
|
4133
|
-
const abs =
|
|
4134
|
-
|
|
4135
|
-
const nodeRootWithSep = nodeRunDir.endsWith(path.sep) ? nodeRunDir : `${nodeRunDir}${path.sep}`;
|
|
4136
|
-
if (!nodeRunDir || (abs !== nodeRunDir && !abs.startsWith(nodeRootWithSep))) return structured;
|
|
4213
|
+
const abs = workspaceNodeOutputWritePath(runPackage, relPath);
|
|
4214
|
+
if (!abs) return structured;
|
|
4137
4215
|
|
|
4138
4216
|
const explicitInlineResult = structured.parsed && typeof structured.parsed === "object"
|
|
4139
4217
|
? String(structured.parsed.result ?? "")
|
|
4140
4218
|
: "";
|
|
4141
4219
|
const content = declared ? explicitInlineResult : String(structured.result ?? "");
|
|
4142
|
-
let primaryReady =
|
|
4220
|
+
let primaryReady = workspaceNodeOutputCandidates(runPackage, relPath)
|
|
4221
|
+
.some((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
4143
4222
|
if (!primaryReady && content.trim()) {
|
|
4144
4223
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
4145
4224
|
const tmp = path.join(path.dirname(abs), `.${path.basename(abs)}.${process.pid}.${Date.now()}.tmp`);
|
|
@@ -4164,8 +4243,8 @@ export function workspaceMaterializeAgentResultFile(structured, runPackage) {
|
|
|
4164
4243
|
if (!outputContent.trim()) continue;
|
|
4165
4244
|
const outputRel = workspaceSafeNodeOutputRelPath(configuredRel);
|
|
4166
4245
|
if (!outputRel) continue;
|
|
4167
|
-
const outputAbs =
|
|
4168
|
-
if (
|
|
4246
|
+
const outputAbs = workspaceNodeOutputWritePath(runPackage, outputRel);
|
|
4247
|
+
if (!outputAbs) continue;
|
|
4169
4248
|
fs.mkdirSync(path.dirname(outputAbs), { recursive: true });
|
|
4170
4249
|
fs.writeFileSync(outputAbs, outputContent, "utf-8");
|
|
4171
4250
|
delete outParams[name];
|
|
@@ -4182,18 +4261,73 @@ export function workspaceMaterializeAgentResultFile(structured, runPackage) {
|
|
|
4182
4261
|
};
|
|
4183
4262
|
}
|
|
4184
4263
|
|
|
4264
|
+
function workspaceCollectNodeOutputFiles(runPackage, maxFiles = 500) {
|
|
4265
|
+
const roots = [
|
|
4266
|
+
runPackage?.outputsDir,
|
|
4267
|
+
runPackage?.nodeRunDir ? path.resolve(runPackage.nodeRunDir, "outputs") : "",
|
|
4268
|
+
].filter((dir, index, list) => dir && list.indexOf(dir) === index && fs.existsSync(dir));
|
|
4269
|
+
const relativeFiles = new Set();
|
|
4270
|
+
const walk = (root, dir, depth = 0) => {
|
|
4271
|
+
if (depth > 12 || relativeFiles.size >= maxFiles) return;
|
|
4272
|
+
let entries = [];
|
|
4273
|
+
try {
|
|
4274
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
4275
|
+
} catch {
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4278
|
+
for (const entry of entries) {
|
|
4279
|
+
if (relativeFiles.size >= maxFiles) break;
|
|
4280
|
+
if (entry.isSymbolicLink()) continue;
|
|
4281
|
+
const abs = path.join(dir, entry.name);
|
|
4282
|
+
if (entry.isDirectory()) {
|
|
4283
|
+
walk(root, abs, depth + 1);
|
|
4284
|
+
} else if (entry.isFile()) {
|
|
4285
|
+
relativeFiles.add(path.relative(root, abs).replace(/\\/g, "/"));
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
};
|
|
4289
|
+
for (const root of roots) walk(root, root);
|
|
4290
|
+
|
|
4291
|
+
const outputFiles = [];
|
|
4292
|
+
for (const relativePath of relativeFiles) {
|
|
4293
|
+
const publishedPath = workspacePublishNodeOutputFile(runPackage, `outputs/${relativePath}`);
|
|
4294
|
+
if (!publishedPath) continue;
|
|
4295
|
+
const publishedAbs = workspaceResolveOutputChild(
|
|
4296
|
+
runPackage.workspaceOutputsDir,
|
|
4297
|
+
publishedPath.slice("outputs/".length),
|
|
4298
|
+
);
|
|
4299
|
+
let size = 0;
|
|
4300
|
+
try {
|
|
4301
|
+
size = publishedAbs ? fs.statSync(publishedAbs).size : 0;
|
|
4302
|
+
} catch {}
|
|
4303
|
+
outputFiles.push({ path: publishedPath, name: path.posix.basename(publishedPath), size });
|
|
4304
|
+
}
|
|
4305
|
+
outputFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
4306
|
+
return outputFiles;
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4185
4309
|
export function workspacePublishAgentOutputFiles(structured, runPackage) {
|
|
4186
|
-
if (!
|
|
4187
|
-
const
|
|
4188
|
-
const
|
|
4310
|
+
if (!runPackage) return structured;
|
|
4311
|
+
const base = structured && typeof structured === "object" ? structured : {};
|
|
4312
|
+
const resultFile = base.structured
|
|
4313
|
+
? workspacePublishNodeOutputFile(runPackage, base.resultFile)
|
|
4314
|
+
: "";
|
|
4315
|
+
const outParams = { ...(base.outParams || {}) };
|
|
4189
4316
|
for (const [key, value] of Object.entries(outParams)) {
|
|
4190
4317
|
if (!String(key || "").endsWith("File")) continue;
|
|
4191
4318
|
const published = workspacePublishNodeOutputFile(runPackage, value);
|
|
4192
4319
|
if (published) outParams[key] = published;
|
|
4193
4320
|
}
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4321
|
+
const outputFiles = workspaceCollectNodeOutputFiles(runPackage);
|
|
4322
|
+
return resultFile || Object.keys(outParams).length || outputFiles.length
|
|
4323
|
+
? {
|
|
4324
|
+
...base,
|
|
4325
|
+
result: resultFile || base.result,
|
|
4326
|
+
resultFile: resultFile || base.resultFile,
|
|
4327
|
+
outParams,
|
|
4328
|
+
outputFiles,
|
|
4329
|
+
}
|
|
4330
|
+
: base;
|
|
4197
4331
|
}
|
|
4198
4332
|
|
|
4199
4333
|
function workspaceOutputFieldForSlot(slot, index = 0) {
|
|
@@ -4679,6 +4813,7 @@ function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
|
|
|
4679
4813
|
function workspaceNodeFileBoundaryBlock(runPackage = {}) {
|
|
4680
4814
|
const nodeRunDir = String(runPackage?.nodeRunDir || "").trim();
|
|
4681
4815
|
const nodeTmpDir = String(runPackage?.nodeTmpDir || "").trim();
|
|
4816
|
+
const outputsDir = String(runPackage?.outputsDir || "").trim();
|
|
4682
4817
|
const outputsRel = String(runPackage?.outputsRel || "outputs").trim() || "outputs";
|
|
4683
4818
|
if (!nodeRunDir && !nodeTmpDir) return "";
|
|
4684
4819
|
return [
|
|
@@ -4688,7 +4823,9 @@ function workspaceNodeFileBoundaryBlock(runPackage = {}) {
|
|
|
4688
4823
|
nodeTmpDir ? `- 临时文件只能写入:\`${nodeTmpDir}\`,也可通过环境变量 \`AGENTFLOW_NODE_TMP_DIR\` 获取。` : "",
|
|
4689
4824
|
Object.keys(runPackage?.inputMounts || {}).length ? "- 已挂载的输入文件位于本任务 `inputs/`;`inputs/` 只用于读取,正式产物仍写入 `outputs/`。" : "",
|
|
4690
4825
|
"- 主文本结果和内联额外输出由 AgentFlow 在任务结束后自动写入、发布和清理,无需自行创建结果文件。",
|
|
4691
|
-
`-
|
|
4826
|
+
outputsDir ? `- 可供用户下载的最终产物目录:\`${outputsDir}\`。这不是临时目录,环境变量 \`AGENTFLOW_OUTPUTS_DIR\` 指向这里。` : "",
|
|
4827
|
+
outputsDir ? `- CSV、图片、压缩包、工程文件等下载产物必须直接写入 \`AGENTFLOW_OUTPUTS_DIR\`,不要写入当前执行目录中的相对 \`outputs/\`。` : "",
|
|
4828
|
+
outputsDir ? `- 该目录中的文件会自动出现在 Workspace Files,对应相对路径为 \`${outputsRel}/\`;回复中引用下载文件时使用这个相对路径。` : "",
|
|
4692
4829
|
"- 不要在执行目录根部创建 `temp_*`、`_out.json`、`tmp.html` 等临时产物。",
|
|
4693
4830
|
"- 不要自行删除 run package;AgentFlow 会在运行结束后统一清理。",
|
|
4694
4831
|
].filter(Boolean).join("\n");
|
|
@@ -5953,14 +6090,20 @@ function workspaceCreateNodeTmpDir(runTmpRoot, nodeId) {
|
|
|
5953
6090
|
return dir;
|
|
5954
6091
|
}
|
|
5955
6092
|
|
|
5956
|
-
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "", resultFile = "", outParamFiles = {} } = {}) {
|
|
6093
|
+
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "", resultFile = "", outParamFiles = {}, durableOutputs = false } = {}) {
|
|
5957
6094
|
const nodeRunDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
5958
6095
|
const nodeTmpDir = path.join(nodeRunDir, "tmp");
|
|
5959
|
-
const
|
|
6096
|
+
const legacyOutputsDir = path.join(nodeRunDir, "outputs");
|
|
5960
6097
|
const workspaceRoot = path.resolve(scopedRoot);
|
|
5961
6098
|
const workspaceOutputsDir = path.join(workspaceRoot, "outputs");
|
|
6099
|
+
const nodePart = workspaceSanitizeTmpSegment(nodeId || "node", "node");
|
|
6100
|
+
const outputsRel = durableOutputs ? path.posix.join("outputs", nodePart) : "outputs";
|
|
6101
|
+
const outputsDir = durableOutputs ? path.join(workspaceOutputsDir, nodePart) : legacyOutputsDir;
|
|
5962
6102
|
const resultFileRel = workspaceSafeNodeOutputRelPath(resultFile) || "";
|
|
5963
|
-
const
|
|
6103
|
+
const resultFileSuffix = workspaceNodeOutputSuffix(resultFileRel);
|
|
6104
|
+
const resultFileAbs = resultFileRel
|
|
6105
|
+
? workspaceResolveOutputChild(outputsDir, resultFileSuffix)
|
|
6106
|
+
: "";
|
|
5964
6107
|
const safeOutParamFiles = {};
|
|
5965
6108
|
for (const [name, rel] of Object.entries(outParamFiles || {})) {
|
|
5966
6109
|
const cleanName = String(name || "").trim();
|
|
@@ -5968,6 +6111,8 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "
|
|
|
5968
6111
|
if (cleanName && cleanRel) safeOutParamFiles[cleanName] = cleanRel;
|
|
5969
6112
|
}
|
|
5970
6113
|
fs.mkdirSync(nodeTmpDir, { recursive: true });
|
|
6114
|
+
fs.mkdirSync(legacyOutputsDir, { recursive: true });
|
|
6115
|
+
if (durableOutputs) fs.rmSync(outputsDir, { recursive: true, force: true });
|
|
5971
6116
|
fs.mkdirSync(outputsDir, { recursive: true });
|
|
5972
6117
|
fs.mkdirSync(workspaceOutputsDir, { recursive: true });
|
|
5973
6118
|
const manifest = {
|
|
@@ -5976,7 +6121,11 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "
|
|
|
5976
6121
|
nodeRunDir,
|
|
5977
6122
|
nodeTmpDir,
|
|
5978
6123
|
outputsDir,
|
|
6124
|
+
legacyOutputsDir,
|
|
6125
|
+
outputsRel,
|
|
6126
|
+
directWorkspaceOutputs: durableOutputs,
|
|
5979
6127
|
workspaceRoot,
|
|
6128
|
+
workspaceOutputsDir,
|
|
5980
6129
|
executionCwd: cwd ? path.resolve(cwd) : workspaceRoot,
|
|
5981
6130
|
resultFileRel,
|
|
5982
6131
|
resultFileAbs,
|
|
@@ -6001,8 +6150,6 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "
|
|
|
6001
6150
|
}
|
|
6002
6151
|
return {
|
|
6003
6152
|
...manifest,
|
|
6004
|
-
workspaceOutputsDir,
|
|
6005
|
-
outputsRel: "outputs",
|
|
6006
6153
|
inputValues: runtimeInputValues,
|
|
6007
6154
|
inputMounts: materializedInputs.mounts,
|
|
6008
6155
|
};
|
|
@@ -6840,6 +6987,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6840
6987
|
cwd,
|
|
6841
6988
|
task: String(instance.script || instance.scriptRef || instance.body || "").trim(),
|
|
6842
6989
|
inputValues,
|
|
6990
|
+
durableOutputs: true,
|
|
6843
6991
|
});
|
|
6844
6992
|
const runtimeInputValues = { ...inputValues, ...(runPackage.inputValues || {}) };
|
|
6845
6993
|
emitTiming(nodeId, "prepare-script", prepareStartedAt, {
|
|
@@ -6876,7 +7024,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6876
7024
|
if (implementationUpdate.changed) graph.instances[nodeId] = implementationUpdate.instance;
|
|
6877
7025
|
const updatedDisplays = propagateNodeOutputDisplays(nodeId, resultContent);
|
|
6878
7026
|
if (slotUpdate.changed || implementationUpdate.changed || updatedDisplays.length) emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
6879
|
-
emit({
|
|
7027
|
+
emit({
|
|
7028
|
+
type: "node-done",
|
|
7029
|
+
nodeId,
|
|
7030
|
+
definitionId: defId,
|
|
7031
|
+
outputFiles: normalizedAgentOutput.outputFiles || [],
|
|
7032
|
+
});
|
|
6880
7033
|
continue;
|
|
6881
7034
|
}
|
|
6882
7035
|
|
|
@@ -6900,6 +7053,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6900
7053
|
mcpBlock: promptMcpBlock,
|
|
6901
7054
|
resultFile: resultOutputSpec.resultFile,
|
|
6902
7055
|
outParamFiles: workspaceOutParamFileSpecs(graph, nodeId),
|
|
7056
|
+
durableOutputs: true,
|
|
6903
7057
|
});
|
|
6904
7058
|
const runtimeInputValues = { ...inputValues, ...(runPackage.inputValues || {}) };
|
|
6905
7059
|
const body = workspaceResolveBodyPlaceholders(instance.body || "", runtimeInputValues).trim();
|
|
@@ -6950,6 +7104,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6950
7104
|
const handle = startComposerAgent({
|
|
6951
7105
|
uiWorkspaceRoot: scopedRoot,
|
|
6952
7106
|
cliWorkspace: runPackage.nodeRunDir,
|
|
7107
|
+
writableDirs: [runPackage.outputsDir],
|
|
6953
7108
|
prompt,
|
|
6954
7109
|
modelKey: nodeModelKey,
|
|
6955
7110
|
agentflowUserId: userCtx.userId || "",
|
|
@@ -7039,7 +7194,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
7039
7194
|
}
|
|
7040
7195
|
const updatedDisplays = propagateNodeOutputDisplays(nodeId, resultContent);
|
|
7041
7196
|
if (slotUpdate.changed || implementationUpdate.changed || contextRunOutputChanged || updatedDisplays.length) emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
7042
|
-
emit({
|
|
7197
|
+
emit({
|
|
7198
|
+
type: "node-done",
|
|
7199
|
+
nodeId,
|
|
7200
|
+
definitionId: defId,
|
|
7201
|
+
outputFiles: normalizedAgentOutput.outputFiles || [],
|
|
7202
|
+
});
|
|
7043
7203
|
}
|
|
7044
7204
|
} finally {
|
|
7045
7205
|
workspaceCleanupAutoWorktrees(autoCleanupWorktrees, graph, emit);
|
|
@@ -7253,14 +7413,22 @@ function resolvePrdWorkflowScope(workspaceRoot, params = {}, userCtx = {}, capab
|
|
|
7253
7413
|
const flowId = String(params.flowId || "").trim();
|
|
7254
7414
|
const flowSource = String(params.flowSource || "user").trim() || "user";
|
|
7255
7415
|
const archived = params.archived === true || params.archived === "1" || params.flowArchived === true;
|
|
7256
|
-
const
|
|
7416
|
+
const shareToken = String(params.workflowShare || params.workflow_share || "").trim();
|
|
7417
|
+
const linkCollaboration = shareToken ? getPrdWorkflowCollaborationByShareToken(shareToken) : null;
|
|
7418
|
+
if (shareToken && (!linkCollaboration || linkCollaboration.tapdId !== tapdId)) {
|
|
7419
|
+
return { error: "Workflow share link is invalid or has been revoked", status: 404 };
|
|
7420
|
+
}
|
|
7421
|
+
const memberCollaboration = tapdId
|
|
7257
7422
|
? getPrdWorkflowCollaborationForUser(tapdId, userCtx?.userId)
|
|
7258
7423
|
: null;
|
|
7259
|
-
const
|
|
7424
|
+
const collaboration = linkCollaboration || memberCollaboration;
|
|
7425
|
+
const access = linkCollaboration
|
|
7426
|
+
? { allowed: true, writable: false, role: "viewer", via: "share-link" }
|
|
7427
|
+
: prdWorkflowCollaborationAccess(collaboration, userCtx?.userId);
|
|
7260
7428
|
if (collaboration && !access.allowed) {
|
|
7261
7429
|
return { error: "PRD Workflow collaboration permission denied", status: 403 };
|
|
7262
7430
|
}
|
|
7263
|
-
if (capability === "write" && collaboration && !access.writable) {
|
|
7431
|
+
if (capability === "write" && (linkCollaboration || (collaboration && !access.writable))) {
|
|
7264
7432
|
return { error: "PRD Workflow collaboration edit permission denied", status: 403 };
|
|
7265
7433
|
}
|
|
7266
7434
|
const ownerId = String(collaboration?.ownerId || userCtx?.userId || "").trim();
|
|
@@ -7287,15 +7455,18 @@ function resolvePrdWorkflowScope(workspaceRoot, params = {}, userCtx = {}, capab
|
|
|
7287
7455
|
ownerId,
|
|
7288
7456
|
collaboration,
|
|
7289
7457
|
collaborationAccess: access,
|
|
7458
|
+
shareToken,
|
|
7459
|
+
sharedByLink: Boolean(linkCollaboration),
|
|
7290
7460
|
flowId,
|
|
7291
7461
|
flowSource,
|
|
7292
7462
|
archived,
|
|
7293
7463
|
};
|
|
7294
7464
|
}
|
|
7295
7465
|
|
|
7296
|
-
function prdWorkflowKey(userCtx = {}, flowSource = "user", flowId = "", tapdId = "") {
|
|
7466
|
+
function prdWorkflowKey(userCtx = {}, flowSource = "user", flowId = "", tapdId = "", shareToken = "") {
|
|
7297
7467
|
const id = String(tapdId || "").trim();
|
|
7298
|
-
const collaboration =
|
|
7468
|
+
const collaboration = getPrdWorkflowCollaborationByShareToken(shareToken)
|
|
7469
|
+
|| getPrdWorkflowCollaborationForUser(id, userCtx?.userId);
|
|
7299
7470
|
const actorScope = `user:${String(collaboration?.ownerId || userCtx?.userId || "")}`;
|
|
7300
7471
|
return [actorScope, id].join("\t");
|
|
7301
7472
|
}
|
|
@@ -8800,6 +8971,19 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
|
|
|
8800
8971
|
incomingImplementationMetadata,
|
|
8801
8972
|
)
|
|
8802
8973
|
: previousImplementationMetadata;
|
|
8974
|
+
const previousGlobalStatePatch =
|
|
8975
|
+
events[index]?.globalStatePatch || events[index]?.global_state_patch;
|
|
8976
|
+
const incomingGlobalStatePatch =
|
|
8977
|
+
entry?.globalStatePatch || entry?.global_state_patch;
|
|
8978
|
+
const mergedGlobalStatePatch =
|
|
8979
|
+
incomingGlobalStatePatch && typeof incomingGlobalStatePatch === "object" && !Array.isArray(incomingGlobalStatePatch)
|
|
8980
|
+
? mergeWorkflowGlobalState(
|
|
8981
|
+
previousGlobalStatePatch && typeof previousGlobalStatePatch === "object" && !Array.isArray(previousGlobalStatePatch)
|
|
8982
|
+
? previousGlobalStatePatch
|
|
8983
|
+
: {},
|
|
8984
|
+
incomingGlobalStatePatch,
|
|
8985
|
+
)
|
|
8986
|
+
: previousGlobalStatePatch;
|
|
8803
8987
|
const prevArtifact = prdWorkflowRuntimeEventArtifactSignature(events[index]);
|
|
8804
8988
|
const nextArtifact = prdWorkflowRuntimeEventArtifactSignature(entry);
|
|
8805
8989
|
artifactConflict = Boolean(prevArtifact && nextArtifact && prevArtifact !== nextArtifact &&
|
|
@@ -8815,9 +8999,14 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
|
|
|
8815
8999
|
...events[index],
|
|
8816
9000
|
...entry,
|
|
8817
9001
|
links: prdWorkflowMergeRuntimeEventArrays(events[index].links, entry.links),
|
|
8818
|
-
artifacts:
|
|
9002
|
+
artifacts: mergeWorkflowArtifactLists(events[index].artifacts, entry.artifacts, entry.artifactScope || "action"),
|
|
8819
9003
|
outputs: prdWorkflowMergeRuntimeEventArrays(events[index].outputs, entry.outputs),
|
|
8820
9004
|
results: prdWorkflowMergeRuntimeEventArrays(events[index].results, entry.results),
|
|
9005
|
+
actionModel: mergeWorkflowGlobalState(events[index].actionModel, entry.actionModel),
|
|
9006
|
+
globalStateRemove: prdWorkflowMergeRuntimeEventArrays(
|
|
9007
|
+
events[index].globalStateRemove || events[index].global_state_remove,
|
|
9008
|
+
entry.globalStateRemove || entry.global_state_remove,
|
|
9009
|
+
),
|
|
8821
9010
|
createdAt: events[index].createdAt || entry.createdAt,
|
|
8822
9011
|
startedAt: events[index].startedAt || entry.startedAt,
|
|
8823
9012
|
idempotencyHistory: [...new Set(idempotencyHistory)].slice(-50),
|
|
@@ -8825,6 +9014,9 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
|
|
|
8825
9014
|
if (mergedImplementationMetadata && typeof mergedImplementationMetadata === "object" && !Array.isArray(mergedImplementationMetadata)) {
|
|
8826
9015
|
events[index].implementationMetadata = mergedImplementationMetadata;
|
|
8827
9016
|
}
|
|
9017
|
+
if (mergedGlobalStatePatch && typeof mergedGlobalStatePatch === "object" && !Array.isArray(mergedGlobalStatePatch)) {
|
|
9018
|
+
events[index].globalStatePatch = mergedGlobalStatePatch;
|
|
9019
|
+
}
|
|
8828
9020
|
if (artifactConflict) {
|
|
8829
9021
|
events[index] = {
|
|
8830
9022
|
...events[index],
|
|
@@ -8915,7 +9107,7 @@ function prdWorkflowMergeRuntimeEventList(snapshotEvents = [], runtimeEvents = [
|
|
|
8915
9107
|
...out[index],
|
|
8916
9108
|
...event,
|
|
8917
9109
|
links: prdWorkflowMergeRuntimeEventArrays(out[index].links, event.links),
|
|
8918
|
-
artifacts:
|
|
9110
|
+
artifacts: mergeWorkflowArtifactLists(out[index].artifacts, event.artifacts, event.artifactScope || "action"),
|
|
8919
9111
|
outputs: prdWorkflowMergeRuntimeEventArrays(out[index].outputs, event.outputs),
|
|
8920
9112
|
results: prdWorkflowMergeRuntimeEventArrays(out[index].results, event.results),
|
|
8921
9113
|
};
|
|
@@ -9123,13 +9315,55 @@ function prdWorkflowOverallFromEvents(tapdId, snapshot = {}, runtimeEvents = [])
|
|
|
9123
9315
|
return prdWorkflowFinalizeOverall(tapdId, overall);
|
|
9124
9316
|
}
|
|
9125
9317
|
|
|
9318
|
+
function prdWorkflowEventUpdatesOverall(event) {
|
|
9319
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) return false;
|
|
9320
|
+
return Boolean(
|
|
9321
|
+
event.overallPatch ||
|
|
9322
|
+
event.overall_patch ||
|
|
9323
|
+
event.overallOwnerFromActor === true ||
|
|
9324
|
+
event.overall_owner_from_actor === true ||
|
|
9325
|
+
event.implementationMetadata ||
|
|
9326
|
+
event.implementation_metadata ||
|
|
9327
|
+
(Array.isArray(event.overallRemove) && event.overallRemove.length) ||
|
|
9328
|
+
(Array.isArray(event.overall_remove) && event.overall_remove.length)
|
|
9329
|
+
);
|
|
9330
|
+
}
|
|
9331
|
+
|
|
9332
|
+
function prdWorkflowGlobalStateFromEvents(tapdId, snapshot = {}, runtimeEvents = []) {
|
|
9333
|
+
let legacyOverall = prdWorkflowOverallFromEvents(tapdId, snapshot, []);
|
|
9334
|
+
let state = materializeWorkflowGlobalState(tapdId, snapshot, [], legacyOverall);
|
|
9335
|
+
const events = [...(Array.isArray(runtimeEvents) ? runtimeEvents : [])].sort((left, right) => {
|
|
9336
|
+
const leftAt = Date.parse(left?.updatedAt || left?.occurredAt || left?.completedAt || left?.createdAt || left?.observedAt || "");
|
|
9337
|
+
const rightAt = Date.parse(right?.updatedAt || right?.occurredAt || right?.completedAt || right?.createdAt || right?.observedAt || "");
|
|
9338
|
+
if (!Number.isFinite(leftAt) && !Number.isFinite(rightAt)) return 0;
|
|
9339
|
+
if (!Number.isFinite(leftAt)) return -1;
|
|
9340
|
+
if (!Number.isFinite(rightAt)) return 1;
|
|
9341
|
+
return leftAt - rightAt;
|
|
9342
|
+
});
|
|
9343
|
+
for (const event of events) {
|
|
9344
|
+
if (prdWorkflowEventUpdatesOverall(event)) {
|
|
9345
|
+
legacyOverall = prdWorkflowOverallFromEvents(tapdId, { overall: legacyOverall }, [event]);
|
|
9346
|
+
state = mergeWorkflowGlobalState(state, legacyOverallToGlobalState(tapdId, legacyOverall));
|
|
9347
|
+
}
|
|
9348
|
+
state = materializeWorkflowGlobalState(tapdId, { globalState: state }, [event], {});
|
|
9349
|
+
}
|
|
9350
|
+
return state;
|
|
9351
|
+
}
|
|
9352
|
+
|
|
9126
9353
|
function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
|
|
9127
9354
|
const runtime = prdWorkflowReadRuntimeEvents(scopedRoot, tapdId);
|
|
9128
9355
|
const runtimeEvents = runtime.events;
|
|
9129
9356
|
const events = prdWorkflowMergeRuntimeEventList(snapshot?.events, runtimeEvents);
|
|
9357
|
+
const overall = prdWorkflowOverallFromEvents(tapdId, snapshot, runtimeEvents);
|
|
9358
|
+
const globalState = prdWorkflowGlobalStateFromEvents(tapdId, snapshot, runtimeEvents);
|
|
9359
|
+
const artifacts = mergeWorkflowArtifacts(snapshot?.artifacts, runtimeEvents);
|
|
9130
9360
|
return {
|
|
9131
9361
|
...snapshot,
|
|
9132
|
-
|
|
9362
|
+
workflow: globalState.workflow,
|
|
9363
|
+
overall,
|
|
9364
|
+
globalState,
|
|
9365
|
+
artifacts,
|
|
9366
|
+
runtimeRevision: workflowRuntimeRevision(globalState, artifacts, runtimeEvents),
|
|
9133
9367
|
runtimeEvents,
|
|
9134
9368
|
events,
|
|
9135
9369
|
sources: {
|
|
@@ -10500,6 +10734,116 @@ export function startUiServer({
|
|
|
10500
10734
|
json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
|
|
10501
10735
|
return;
|
|
10502
10736
|
}
|
|
10737
|
+
if (req.method === "GET" && url.pathname === "/api/prd-workflow/share") {
|
|
10738
|
+
const tapdId = String(url.searchParams.get("tapdId") || "").trim();
|
|
10739
|
+
const shareToken = String(url.searchParams.get("workflowShare") || "").trim();
|
|
10740
|
+
if (shareToken) {
|
|
10741
|
+
const record = getPrdWorkflowCollaborationByShareToken(shareToken);
|
|
10742
|
+
if (!record || (tapdId && record.tapdId !== tapdId)) {
|
|
10743
|
+
json(res, 404, { error: "Workflow share link is invalid or has been revoked" });
|
|
10744
|
+
return;
|
|
10745
|
+
}
|
|
10746
|
+
json(res, 200, {
|
|
10747
|
+
ok: true,
|
|
10748
|
+
share: prdWorkflowShareLinkSummary(
|
|
10749
|
+
record,
|
|
10750
|
+
shareToken,
|
|
10751
|
+
serverPublicBaseUrl(req, host, uiPort),
|
|
10752
|
+
userCtx.userId,
|
|
10753
|
+
),
|
|
10754
|
+
});
|
|
10755
|
+
return;
|
|
10756
|
+
}
|
|
10757
|
+
if (!authUser?.userId) {
|
|
10758
|
+
json(res, 401, { error: "Unauthorized" });
|
|
10759
|
+
return;
|
|
10760
|
+
}
|
|
10761
|
+
if (!tapdId) {
|
|
10762
|
+
json(res, 400, { error: "Missing tapdId" });
|
|
10763
|
+
return;
|
|
10764
|
+
}
|
|
10765
|
+
const record = getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId);
|
|
10766
|
+
const role = prdWorkflowCollaborationAccess(record, userCtx.userId).role;
|
|
10767
|
+
json(res, 200, {
|
|
10768
|
+
ok: true,
|
|
10769
|
+
canCreate: !record || role === "owner",
|
|
10770
|
+
share: record?.shareToken
|
|
10771
|
+
? prdWorkflowShareLinkSummary(
|
|
10772
|
+
record,
|
|
10773
|
+
record.shareToken,
|
|
10774
|
+
serverPublicBaseUrl(req, host, uiPort),
|
|
10775
|
+
userCtx.userId,
|
|
10776
|
+
)
|
|
10777
|
+
: null,
|
|
10778
|
+
});
|
|
10779
|
+
return;
|
|
10780
|
+
}
|
|
10781
|
+
if (req.method === "POST" && url.pathname === "/api/prd-workflow/share") {
|
|
10782
|
+
let payload;
|
|
10783
|
+
try {
|
|
10784
|
+
payload = JSON.parse(await readBody(req));
|
|
10785
|
+
} catch {
|
|
10786
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
10787
|
+
return;
|
|
10788
|
+
}
|
|
10789
|
+
if (!authUser?.userId) {
|
|
10790
|
+
json(res, 401, { error: "Unauthorized" });
|
|
10791
|
+
return;
|
|
10792
|
+
}
|
|
10793
|
+
const tapdId = String(payload?.tapdId || payload?.tapd_id || "").trim();
|
|
10794
|
+
if (!tapdId) {
|
|
10795
|
+
json(res, 400, { error: "Missing tapdId" });
|
|
10796
|
+
return;
|
|
10797
|
+
}
|
|
10798
|
+
const existing = getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId);
|
|
10799
|
+
if (existing && prdWorkflowCollaborationAccess(existing, userCtx.userId).role !== "owner") {
|
|
10800
|
+
json(res, 403, { error: "仅 Workflow 所有者可以创建分享链接" });
|
|
10801
|
+
return;
|
|
10802
|
+
}
|
|
10803
|
+
const result = ensurePrdWorkflowShareLink({ tapdId, userId: userCtx.userId });
|
|
10804
|
+
if (result.error) {
|
|
10805
|
+
json(res, result.status || 400, { error: result.error });
|
|
10806
|
+
return;
|
|
10807
|
+
}
|
|
10808
|
+
const scope = resolvePrdWorkflowScope(root, { ...payload, tapdId }, userCtx, "write");
|
|
10809
|
+
if (!scope.error) prdWorkflowMigrateLegacyState(scope.executionRoot, scope.stateRoot, tapdId);
|
|
10810
|
+
json(res, 200, {
|
|
10811
|
+
ok: true,
|
|
10812
|
+
created: result.created === true,
|
|
10813
|
+
share: prdWorkflowShareLinkSummary(
|
|
10814
|
+
result.record,
|
|
10815
|
+
result.shareToken,
|
|
10816
|
+
serverPublicBaseUrl(req, host, uiPort, payload),
|
|
10817
|
+
userCtx.userId,
|
|
10818
|
+
),
|
|
10819
|
+
});
|
|
10820
|
+
return;
|
|
10821
|
+
}
|
|
10822
|
+
if (req.method === "DELETE" && url.pathname === "/api/prd-workflow/share") {
|
|
10823
|
+
let payload;
|
|
10824
|
+
try {
|
|
10825
|
+
payload = JSON.parse(await readBody(req));
|
|
10826
|
+
} catch {
|
|
10827
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
10828
|
+
return;
|
|
10829
|
+
}
|
|
10830
|
+
if (!authUser?.userId) {
|
|
10831
|
+
json(res, 401, { error: "Unauthorized" });
|
|
10832
|
+
return;
|
|
10833
|
+
}
|
|
10834
|
+
const tapdId = String(payload?.tapdId || payload?.tapd_id || "").trim();
|
|
10835
|
+
if (!tapdId) {
|
|
10836
|
+
json(res, 400, { error: "Missing tapdId" });
|
|
10837
|
+
return;
|
|
10838
|
+
}
|
|
10839
|
+
const result = revokePrdWorkflowShareLink({ tapdId, userId: userCtx.userId });
|
|
10840
|
+
if (result.error) {
|
|
10841
|
+
json(res, result.status || 400, { error: result.error });
|
|
10842
|
+
return;
|
|
10843
|
+
}
|
|
10844
|
+
json(res, 200, { ok: true, revoked: result.revoked === true, share: null });
|
|
10845
|
+
return;
|
|
10846
|
+
}
|
|
10503
10847
|
if (req.method === "GET" && url.pathname === "/api/prd-workflow/collaboration") {
|
|
10504
10848
|
const tapdId = String(url.searchParams.get("tapdId") || "").trim();
|
|
10505
10849
|
if (!tapdId) {
|
|
@@ -10611,6 +10955,55 @@ export function startUiServer({
|
|
|
10611
10955
|
}
|
|
10612
10956
|
return;
|
|
10613
10957
|
}
|
|
10958
|
+
if (req.method === "GET" && url.pathname === "/api/workflows/state") {
|
|
10959
|
+
try {
|
|
10960
|
+
const workflow = normalizeWorkflowReference({
|
|
10961
|
+
workflow: {
|
|
10962
|
+
key: url.searchParams.get("workflow") || "",
|
|
10963
|
+
namespace: url.searchParams.get("namespace") || "",
|
|
10964
|
+
id: url.searchParams.get("id") || "",
|
|
10965
|
+
},
|
|
10966
|
+
});
|
|
10967
|
+
if (workflow.error) {
|
|
10968
|
+
json(res, 400, { error: workflow.error });
|
|
10969
|
+
return;
|
|
10970
|
+
}
|
|
10971
|
+
if (workflow.namespace !== "tapd") {
|
|
10972
|
+
json(res, 400, { error: `Unsupported workflow namespace: ${workflow.namespace}` });
|
|
10973
|
+
return;
|
|
10974
|
+
}
|
|
10975
|
+
const flowId = String(url.searchParams.get("flowId") || "").trim();
|
|
10976
|
+
const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
|
|
10977
|
+
const workflowScope = resolvePrdWorkflowScope(root, {
|
|
10978
|
+
tapdId: workflow.id,
|
|
10979
|
+
flowId,
|
|
10980
|
+
flowSource,
|
|
10981
|
+
archived: url.searchParams.get("archived") === "1",
|
|
10982
|
+
workspaceId: url.searchParams.get("workspaceId") || "",
|
|
10983
|
+
workflowShare: url.searchParams.get("workflowShare") || "",
|
|
10984
|
+
}, userCtx);
|
|
10985
|
+
if (workflowScope.error) {
|
|
10986
|
+
json(res, workflowScope.status || 400, { error: workflowScope.error });
|
|
10987
|
+
return;
|
|
10988
|
+
}
|
|
10989
|
+
const scopedRoot = workflowScope.stateRoot;
|
|
10990
|
+
prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, workflow.id);
|
|
10991
|
+
const runtimeOnly = url.searchParams.get("runtimeOnly") === "1" ||
|
|
10992
|
+
url.searchParams.get("runtime_only") === "1" ||
|
|
10993
|
+
url.searchParams.get("cached") === "1";
|
|
10994
|
+
const baseSnapshot = runtimeOnly
|
|
10995
|
+
? prdWorkflowMaterializeSnapshot(workflowScope.executionRoot, scopedRoot, workflow.id, userCtx, { flowSource, flowId })
|
|
10996
|
+
: await prdWorkflowSnapshot(workflowScope.executionRoot, scopedRoot, workflow.id, userCtx, { flowSource, flowId });
|
|
10997
|
+
const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
|
|
10998
|
+
baseSnapshot,
|
|
10999
|
+
getSessionTokenFromRequest(req) || "",
|
|
11000
|
+
);
|
|
11001
|
+
json(res, 200, { ok: true, workflow, snapshot });
|
|
11002
|
+
} catch (e) {
|
|
11003
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
11004
|
+
}
|
|
11005
|
+
return;
|
|
11006
|
+
}
|
|
10614
11007
|
if (req.method === "GET" && url.pathname === "/api/prd-workflow/snapshot") {
|
|
10615
11008
|
try {
|
|
10616
11009
|
const tapdId = String(url.searchParams.get("tapdId") || "").trim();
|
|
@@ -10623,6 +11016,7 @@ export function startUiServer({
|
|
|
10623
11016
|
flowSource,
|
|
10624
11017
|
archived,
|
|
10625
11018
|
workspaceId: url.searchParams.get("workspaceId") || "",
|
|
11019
|
+
workflowShare: url.searchParams.get("workflowShare") || "",
|
|
10626
11020
|
}, userCtx);
|
|
10627
11021
|
if (workflowScope.error) {
|
|
10628
11022
|
json(res, workflowScope.status || 400, { error: workflowScope.error });
|
|
@@ -10651,6 +11045,10 @@ export function startUiServer({
|
|
|
10651
11045
|
}
|
|
10652
11046
|
|
|
10653
11047
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/snapshot") {
|
|
11048
|
+
if (!authUser?.userId) {
|
|
11049
|
+
json(res, 401, { error: "Authentication required" });
|
|
11050
|
+
return;
|
|
11051
|
+
}
|
|
10654
11052
|
let payload;
|
|
10655
11053
|
try {
|
|
10656
11054
|
payload = JSON.parse(await readBody(req));
|
|
@@ -10815,6 +11213,10 @@ export function startUiServer({
|
|
|
10815
11213
|
}
|
|
10816
11214
|
|
|
10817
11215
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/action") {
|
|
11216
|
+
if (!authUser?.userId) {
|
|
11217
|
+
json(res, 401, { error: "Authentication required" });
|
|
11218
|
+
return;
|
|
11219
|
+
}
|
|
10818
11220
|
let payload;
|
|
10819
11221
|
try {
|
|
10820
11222
|
payload = JSON.parse(await readBody(req));
|
|
@@ -11237,6 +11639,10 @@ export function startUiServer({
|
|
|
11237
11639
|
}
|
|
11238
11640
|
|
|
11239
11641
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/idempotency") {
|
|
11642
|
+
if (!authUser?.userId) {
|
|
11643
|
+
json(res, 401, { error: "Authentication required" });
|
|
11644
|
+
return;
|
|
11645
|
+
}
|
|
11240
11646
|
let payload;
|
|
11241
11647
|
try {
|
|
11242
11648
|
payload = JSON.parse(await readBody(req));
|
|
@@ -11304,7 +11710,117 @@ export function startUiServer({
|
|
|
11304
11710
|
return;
|
|
11305
11711
|
}
|
|
11306
11712
|
|
|
11713
|
+
if (req.method === "POST" && url.pathname === "/api/workflows/report") {
|
|
11714
|
+
if (!authUser?.userId) {
|
|
11715
|
+
json(res, 401, { error: "Authentication required" });
|
|
11716
|
+
return;
|
|
11717
|
+
}
|
|
11718
|
+
let payload;
|
|
11719
|
+
try {
|
|
11720
|
+
payload = JSON.parse(await readBody(req));
|
|
11721
|
+
} catch {
|
|
11722
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
11723
|
+
return;
|
|
11724
|
+
}
|
|
11725
|
+
try {
|
|
11726
|
+
const report = normalizeWorkflowReport(payload);
|
|
11727
|
+
if (report.error) {
|
|
11728
|
+
json(res, 400, { error: report.error });
|
|
11729
|
+
return;
|
|
11730
|
+
}
|
|
11731
|
+
if (report.workflow.namespace !== "tapd") {
|
|
11732
|
+
json(res, 400, { error: `Unsupported workflow namespace: ${report.workflow.namespace}` });
|
|
11733
|
+
return;
|
|
11734
|
+
}
|
|
11735
|
+
const tapdId = report.workflow.id;
|
|
11736
|
+
const flowId = report.flowId;
|
|
11737
|
+
const flowSource = report.flowSource || "user";
|
|
11738
|
+
const archived = payload.archived === true || payload.flowArchived === true;
|
|
11739
|
+
const workflowScope = resolvePrdWorkflowScope(root, {
|
|
11740
|
+
...payload,
|
|
11741
|
+
tapdId,
|
|
11742
|
+
flowId,
|
|
11743
|
+
flowSource,
|
|
11744
|
+
archived,
|
|
11745
|
+
}, userCtx, "write");
|
|
11746
|
+
if (workflowScope.error) {
|
|
11747
|
+
json(res, workflowScope.status || 400, { error: workflowScope.error });
|
|
11748
|
+
return;
|
|
11749
|
+
}
|
|
11750
|
+
const scopedRoot = workflowScope.stateRoot;
|
|
11751
|
+
prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
|
|
11752
|
+
const currentSnapshot = prdWorkflowMaterializeSnapshot(
|
|
11753
|
+
workflowScope.executionRoot,
|
|
11754
|
+
scopedRoot,
|
|
11755
|
+
tapdId,
|
|
11756
|
+
userCtx,
|
|
11757
|
+
{ flowSource, flowId },
|
|
11758
|
+
);
|
|
11759
|
+
const acceptedRevisions = new Set([
|
|
11760
|
+
String(currentSnapshot.runtimeRevision || "").trim(),
|
|
11761
|
+
String(currentSnapshot.revision || "").trim(),
|
|
11762
|
+
].filter(Boolean));
|
|
11763
|
+
if (report.expectedRevision && acceptedRevisions.size && !acceptedRevisions.has(report.expectedRevision)) {
|
|
11764
|
+
json(res, 409, {
|
|
11765
|
+
error: "Workflow state changed; refresh before reporting",
|
|
11766
|
+
conflict: {
|
|
11767
|
+
type: "workflow-revision-conflict",
|
|
11768
|
+
expectedRevision: report.expectedRevision,
|
|
11769
|
+
currentRevision: currentSnapshot.runtimeRevision || currentSnapshot.revision || "",
|
|
11770
|
+
workflow: report.workflow,
|
|
11771
|
+
},
|
|
11772
|
+
snapshot: currentSnapshot,
|
|
11773
|
+
});
|
|
11774
|
+
return;
|
|
11775
|
+
}
|
|
11776
|
+
if (report.idempotencyKey) {
|
|
11777
|
+
const existing = prdWorkflowFindCompletedIdempotencyEvent(scopedRoot, tapdId, report.idempotencyKey);
|
|
11778
|
+
if (existing) {
|
|
11779
|
+
json(res, 200, {
|
|
11780
|
+
ok: true,
|
|
11781
|
+
alreadyApplied: true,
|
|
11782
|
+
report,
|
|
11783
|
+
event: existing,
|
|
11784
|
+
snapshot: currentSnapshot,
|
|
11785
|
+
});
|
|
11786
|
+
return;
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
const event = prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, {
|
|
11790
|
+
...report.event,
|
|
11791
|
+
tapdId,
|
|
11792
|
+
actor: {
|
|
11793
|
+
userId: String(userCtx.userId || ""),
|
|
11794
|
+
username: String(authUser.username || userCtx.userId || ""),
|
|
11795
|
+
},
|
|
11796
|
+
});
|
|
11797
|
+
if (!event) throw new Error("Failed to store workflow report");
|
|
11798
|
+
const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
|
|
11799
|
+
prdWorkflowMaterializeSnapshot(
|
|
11800
|
+
workflowScope.executionRoot,
|
|
11801
|
+
scopedRoot,
|
|
11802
|
+
tapdId,
|
|
11803
|
+
userCtx,
|
|
11804
|
+
{ flowSource, flowId },
|
|
11805
|
+
),
|
|
11806
|
+
getSessionTokenFromRequest(req) || "",
|
|
11807
|
+
);
|
|
11808
|
+
prdWorkflowBroadcast(
|
|
11809
|
+
prdWorkflowKey(userCtx, flowSource, flowId, tapdId),
|
|
11810
|
+
{ type: "workflow-report", tapdId, workflow: report.workflow, event, snapshot },
|
|
11811
|
+
);
|
|
11812
|
+
json(res, 200, { ok: true, report, event, snapshot });
|
|
11813
|
+
} catch (e) {
|
|
11814
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
11815
|
+
}
|
|
11816
|
+
return;
|
|
11817
|
+
}
|
|
11818
|
+
|
|
11307
11819
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/event") {
|
|
11820
|
+
if (!authUser?.userId) {
|
|
11821
|
+
json(res, 401, { error: "Authentication required" });
|
|
11822
|
+
return;
|
|
11823
|
+
}
|
|
11308
11824
|
let payload;
|
|
11309
11825
|
try {
|
|
11310
11826
|
payload = JSON.parse(await readBody(req));
|
|
@@ -11359,6 +11875,10 @@ export function startUiServer({
|
|
|
11359
11875
|
}
|
|
11360
11876
|
|
|
11361
11877
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/review-link") {
|
|
11878
|
+
if (!authUser?.userId) {
|
|
11879
|
+
json(res, 401, { error: "Authentication required" });
|
|
11880
|
+
return;
|
|
11881
|
+
}
|
|
11362
11882
|
let payload;
|
|
11363
11883
|
try {
|
|
11364
11884
|
payload = JSON.parse(await readBody(req));
|
|
@@ -11439,7 +11959,18 @@ export function startUiServer({
|
|
|
11439
11959
|
const tapdId = String(url.searchParams.get("tapdId") || "").trim();
|
|
11440
11960
|
const flowId = String(url.searchParams.get("flowId") || "").trim();
|
|
11441
11961
|
const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
|
|
11442
|
-
const
|
|
11962
|
+
const workflowShare = String(url.searchParams.get("workflowShare") || "").trim();
|
|
11963
|
+
const workflowScope = resolvePrdWorkflowScope(root, {
|
|
11964
|
+
tapdId,
|
|
11965
|
+
flowId,
|
|
11966
|
+
flowSource,
|
|
11967
|
+
workflowShare,
|
|
11968
|
+
}, userCtx);
|
|
11969
|
+
if (workflowScope.error) {
|
|
11970
|
+
json(res, workflowScope.status || 400, { error: workflowScope.error });
|
|
11971
|
+
return;
|
|
11972
|
+
}
|
|
11973
|
+
const key = prdWorkflowKey(userCtx, flowSource, flowId, tapdId, workflowShare);
|
|
11443
11974
|
let set = prdWorkflowSubscribers.get(key);
|
|
11444
11975
|
if (!set) {
|
|
11445
11976
|
set = new Set();
|
|
@@ -11483,6 +12014,7 @@ export function startUiServer({
|
|
|
11483
12014
|
flowSource,
|
|
11484
12015
|
archived,
|
|
11485
12016
|
workspaceId: url.searchParams.get("workspaceId") || "",
|
|
12017
|
+
workflowShare: url.searchParams.get("workflowShare") || "",
|
|
11486
12018
|
}, userCtx);
|
|
11487
12019
|
if (workflowScope.error) {
|
|
11488
12020
|
res.writeHead(workflowScope.status || 400, { "Content-Type": "text/plain; charset=utf-8" });
|
|
@@ -13850,7 +14382,10 @@ export function startUiServer({
|
|
|
13850
14382
|
|
|
13851
14383
|
if (req.method === "GET" && url.pathname === "/api/ui-context") {
|
|
13852
14384
|
try {
|
|
13853
|
-
json(res, 200, {
|
|
14385
|
+
json(res, 200, {
|
|
14386
|
+
...uiConfig,
|
|
14387
|
+
...(authUser?.isAdmin ? { workspaceRoot: root } : {}),
|
|
14388
|
+
});
|
|
13854
14389
|
} catch (e) {
|
|
13855
14390
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
13856
14391
|
}
|
|
@@ -13920,6 +14455,10 @@ export function startUiServer({
|
|
|
13920
14455
|
}
|
|
13921
14456
|
|
|
13922
14457
|
if (req.method === "GET" && url.pathname === "/api/agentflow-config") {
|
|
14458
|
+
if (!authUser?.isAdmin) {
|
|
14459
|
+
json(res, 403, { error: "Admin permission required" });
|
|
14460
|
+
return;
|
|
14461
|
+
}
|
|
13923
14462
|
try {
|
|
13924
14463
|
const cfg = readAgentflowUserConfigObject();
|
|
13925
14464
|
const opencodeProvider = typeof cfg.opencodeProvider === "string" ? cfg.opencodeProvider : "";
|
|
@@ -13931,6 +14470,10 @@ export function startUiServer({
|
|
|
13931
14470
|
}
|
|
13932
14471
|
|
|
13933
14472
|
if (req.method === "POST" && url.pathname === "/api/agentflow-config") {
|
|
14473
|
+
if (!authUser?.isAdmin) {
|
|
14474
|
+
json(res, 403, { error: "Admin permission required" });
|
|
14475
|
+
return;
|
|
14476
|
+
}
|
|
13934
14477
|
let payload;
|
|
13935
14478
|
try {
|
|
13936
14479
|
payload = JSON.parse(await readBody(req));
|
|
@@ -14020,8 +14563,11 @@ export function startUiServer({
|
|
|
14020
14563
|
|
|
14021
14564
|
if (req.method === "GET" && url.pathname === "/api/user-env") {
|
|
14022
14565
|
try {
|
|
14566
|
+
const userEnvRows = readUserEnvRows(userCtx.userId);
|
|
14023
14567
|
json(res, 200, {
|
|
14024
|
-
env:
|
|
14568
|
+
env: authUser?.isAdmin
|
|
14569
|
+
? userEnvRows
|
|
14570
|
+
: userEnvRows.filter((row) => !ADMIN_ONLY_USER_ENV_KEYS.has(String(row?.key || "").trim())),
|
|
14025
14571
|
globalEnv: authUser?.isAdmin ? readGlobalEnvRows() : [],
|
|
14026
14572
|
canEditGlobalEnv: Boolean(authUser?.isAdmin),
|
|
14027
14573
|
});
|
|
@@ -14044,13 +14590,23 @@ export function startUiServer({
|
|
|
14044
14590
|
json(res, 403, { error: "Admin permission required" });
|
|
14045
14591
|
return;
|
|
14046
14592
|
}
|
|
14047
|
-
const
|
|
14593
|
+
const requestedEnvRows = Array.isArray(payload?.env) ? payload.env : [];
|
|
14594
|
+
if (!authUser?.isAdmin && requestedEnvRows.some((row) => ADMIN_ONLY_USER_ENV_KEYS.has(String(row?.key || "").trim()))) {
|
|
14595
|
+
json(res, 403, { error: "Admin permission required for infrastructure environment keys" });
|
|
14596
|
+
return;
|
|
14597
|
+
}
|
|
14598
|
+
const preservedAdminRows = authUser?.isAdmin
|
|
14599
|
+
? []
|
|
14600
|
+
: readUserEnvRows(userCtx.userId).filter((row) => ADMIN_ONLY_USER_ENV_KEYS.has(String(row?.key || "").trim()));
|
|
14601
|
+
const envRows = writeUserEnvRows(userCtx.userId, [...preservedAdminRows, ...requestedEnvRows]);
|
|
14048
14602
|
const globalEnvRows = authUser?.isAdmin && Object.prototype.hasOwnProperty.call(payload || {}, "globalEnv")
|
|
14049
14603
|
? writeGlobalEnvRows(payload?.globalEnv || [])
|
|
14050
14604
|
: readGlobalEnvRows();
|
|
14051
14605
|
json(res, 200, {
|
|
14052
14606
|
success: true,
|
|
14053
|
-
env:
|
|
14607
|
+
env: authUser?.isAdmin
|
|
14608
|
+
? envRows
|
|
14609
|
+
: envRows.filter((row) => !ADMIN_ONLY_USER_ENV_KEYS.has(String(row?.key || "").trim())),
|
|
14054
14610
|
globalEnv: authUser?.isAdmin ? globalEnvRows : [],
|
|
14055
14611
|
canEditGlobalEnv: Boolean(authUser?.isAdmin),
|
|
14056
14612
|
});
|
|
@@ -14061,6 +14617,10 @@ export function startUiServer({
|
|
|
14061
14617
|
}
|
|
14062
14618
|
|
|
14063
14619
|
if (req.method === "POST" && url.pathname === "/api/update-model-lists") {
|
|
14620
|
+
if (!authUser?.isAdmin) {
|
|
14621
|
+
json(res, 403, { error: "Admin permission required" });
|
|
14622
|
+
return;
|
|
14623
|
+
}
|
|
14064
14624
|
try {
|
|
14065
14625
|
let opencodeProviderOverride = "";
|
|
14066
14626
|
const raw = await readBody(req);
|