@fieldwangai/agentflow 0.1.162 → 0.1.163
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/flow-dsl/codegen.mjs +23 -2
- package/bin/lib/flow-dsl/lint.mjs +44 -0
- package/bin/lib/flow-dsl/parser.mjs +68 -1
- package/bin/lib/marketplace-usage.mjs +218 -0
- package/bin/lib/marketplace.mjs +183 -3
- package/bin/lib/node-package-manifest.mjs +1 -1
- package/bin/lib/spaces.mjs +200 -0
- package/bin/lib/ui-server.mjs +251 -67
- package/bin/lib/workspace-routes.mjs +189 -2
- package/bin/lib/workspace-server.mjs +367 -30
- package/bin/lib/workspace-state.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +5 -1
- package/builtin/nodes/context_bundle.md +44 -0
- package/builtin/nodes/context_knowledge.md +29 -0
- package/builtin/nodes/context_skills.md +29 -0
- package/builtin/nodes/context_workspace.md +36 -0
- package/builtin/nodes/control_while.md +7 -0
- package/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CTrXOZ00.js → WorkflowAssistantThread-DmhIgoD7.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CI9J6Unt.js +873 -0
- package/builtin/web-ui/dist/assets/index-DY5vE7v1.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/shared/slot-types.js +1 -0
- package/skills/agentflow-cli/SKILL.md +50 -3
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +120 -6
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +5 -1
- package/skills/agentflow-cli/runtime/builtin/nodes/context_bundle.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_knowledge.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_skills.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_workspace.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +7 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/skills/agentflow-cli/runtime/package.json +1 -1
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +64 -0
- package/skills/agentflow-flow-dsl/SKILL.md +48 -2
- package/skills/agentflow-flow-dsl/references/node-calls.md +6 -2
- package/skills/agentflow-flow-dsl/references/subflow-authoring.md +34 -7
- package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
- package/builtin/web-ui/dist/assets/index-5uJFccdX.css +0 -1
- package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +0 -873
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -130,7 +130,6 @@ import { json, readBody } from "./http-util.mjs";
|
|
|
130
130
|
|
|
131
131
|
// 从 ui-server 拆出去的 Workspace 子系统;路由仍在下面的 startUiServer 里
|
|
132
132
|
import {
|
|
133
|
-
USER_WORKSPACES_FILENAME,
|
|
134
133
|
WORKSPACE_DEFERRED_RUN_POLL_MS,
|
|
135
134
|
WORKSPACE_SCHEDULE_POLL_MS,
|
|
136
135
|
activeWorkspaceRunUsageRecords,
|
|
@@ -141,6 +140,7 @@ import {
|
|
|
141
140
|
displayShareOutputUrl,
|
|
142
141
|
isReadonlyBuiltinFlowSource,
|
|
143
142
|
isValidFlowSourceRead,
|
|
143
|
+
listConfiguredWorkspaces,
|
|
144
144
|
listWorkspaceScheduleStatuses,
|
|
145
145
|
normalizeDisplayShareExpiry,
|
|
146
146
|
normalizeDisplayShareNodeIds,
|
|
@@ -151,11 +151,11 @@ import {
|
|
|
151
151
|
readCursorMcpConfig,
|
|
152
152
|
readCursorMcpServers,
|
|
153
153
|
readDisplayShares,
|
|
154
|
+
readUserWorkspaces,
|
|
154
155
|
readUserMcpPrivate,
|
|
155
156
|
readWorkspaceGraph,
|
|
156
157
|
readWorkspaceRunUsageRecords,
|
|
157
158
|
readWorkspaceScheduleRegistry,
|
|
158
|
-
readWorkspacesFromPath,
|
|
159
159
|
resolveWorkspaceFilePath,
|
|
160
160
|
resolveWorkspaceScopeRoot,
|
|
161
161
|
runStatusBucket,
|
|
@@ -173,11 +173,22 @@ import {
|
|
|
173
173
|
workspaceDownloadContentDisposition,
|
|
174
174
|
workspaceFlowCollaborationGuard,
|
|
175
175
|
workspaceScheduleNextRunAt,
|
|
176
|
-
workspacesPath,
|
|
177
176
|
writeDisplayShares,
|
|
178
177
|
writeWorkspaceGraph,
|
|
179
178
|
} from "./workspace-server.mjs";
|
|
180
179
|
import { handleWorkspaceRoutes, workspaceGraphWithScheduleMode } from "./workspace-routes.mjs";
|
|
180
|
+
import {
|
|
181
|
+
createSpace,
|
|
182
|
+
deleteSpacePage,
|
|
183
|
+
getSpaceById,
|
|
184
|
+
getSpaceByRoute,
|
|
185
|
+
listSpacesForUser,
|
|
186
|
+
readSpaces,
|
|
187
|
+
normalizeSpacePagePath,
|
|
188
|
+
normalizeSpaceVisibility,
|
|
189
|
+
updateSpace,
|
|
190
|
+
upsertSpacePage,
|
|
191
|
+
} from "./spaces.mjs";
|
|
181
192
|
|
|
182
193
|
const MIME = {
|
|
183
194
|
".html": "text/html; charset=utf-8",
|
|
@@ -935,70 +946,6 @@ function skillhubInstallArgs(payload, { uninstall = false } = {}) {
|
|
|
935
946
|
}
|
|
936
947
|
|
|
937
948
|
const DISPLAY_SHARE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
938
|
-
function legacyUserWorkspacesPath(userCtx = {}) {
|
|
939
|
-
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
function readLegacyAdminWorkspaces(userCtx = {}) {
|
|
943
|
-
const users = readAuthUsers();
|
|
944
|
-
const candidates = [];
|
|
945
|
-
for (const [userId, user] of Object.entries(users || {})) {
|
|
946
|
-
if (user?.isAdmin) candidates.push(String(userId || ""));
|
|
947
|
-
}
|
|
948
|
-
if (userCtx?.isAdmin && userCtx.userId) candidates.unshift(String(userCtx.userId));
|
|
949
|
-
const seenPaths = new Set();
|
|
950
|
-
const seenEntries = new Set();
|
|
951
|
-
const out = [];
|
|
952
|
-
for (const userId of candidates) {
|
|
953
|
-
const p = legacyUserWorkspacesPath({ userId });
|
|
954
|
-
const resolved = path.resolve(p);
|
|
955
|
-
if (seenPaths.has(resolved) || resolved === path.resolve(workspacesPath())) continue;
|
|
956
|
-
seenPaths.add(resolved);
|
|
957
|
-
for (const entry of readWorkspacesFromPath(p, { userId })) {
|
|
958
|
-
const key = entry.id || entry.path || entry.repoUrl;
|
|
959
|
-
if (seenEntries.has(key)) continue;
|
|
960
|
-
seenEntries.add(key);
|
|
961
|
-
out.push(entry);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return out;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function readUserWorkspaces(userCtx = {}) {
|
|
968
|
-
const globalPath = workspacesPath();
|
|
969
|
-
const globalWorkspaces = fs.existsSync(globalPath) ? readWorkspacesFromPath(globalPath, userCtx) : [];
|
|
970
|
-
const adminLegacy = readLegacyAdminWorkspaces(userCtx);
|
|
971
|
-
if (globalWorkspaces.length || adminLegacy.length) {
|
|
972
|
-
const seen = new Set();
|
|
973
|
-
const out = [];
|
|
974
|
-
for (const entry of [...globalWorkspaces, ...adminLegacy]) {
|
|
975
|
-
const key = entry.id || entry.path || entry.repoUrl;
|
|
976
|
-
if (seen.has(key)) continue;
|
|
977
|
-
seen.add(key);
|
|
978
|
-
out.push(entry);
|
|
979
|
-
}
|
|
980
|
-
return out;
|
|
981
|
-
}
|
|
982
|
-
return readWorkspacesFromPath(legacyUserWorkspacesPath(userCtx), userCtx);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function listConfiguredWorkspaces(root, scopedRoot, userCtx = {}) {
|
|
986
|
-
const currentRoot = path.resolve(scopedRoot || root);
|
|
987
|
-
const homeRoot = path.resolve(os.homedir());
|
|
988
|
-
const builtins = [
|
|
989
|
-
{ id: "current", label: "当前流程工作区", kind: "local", path: currentRoot, builtin: true, exists: fs.existsSync(currentRoot) && fs.statSync(currentRoot).isDirectory(), type: "flow", enabled: true },
|
|
990
|
-
{ id: "home", label: "用户 Home", kind: "local", path: homeRoot, builtin: true, exists: fs.existsSync(homeRoot) && fs.statSync(homeRoot).isDirectory(), type: "local", enabled: true },
|
|
991
|
-
];
|
|
992
|
-
const custom = readUserWorkspaces(userCtx).filter((entry) => entry.enabled !== false).map((entry) => ({ ...entry, builtin: false }));
|
|
993
|
-
const seen = new Set();
|
|
994
|
-
return [...builtins, ...custom].filter((entry) => {
|
|
995
|
-
const key = path.resolve(entry.path);
|
|
996
|
-
if (seen.has(key)) return false;
|
|
997
|
-
seen.add(key);
|
|
998
|
-
return true;
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
949
|
function workflowBindableWorkspaces(userCtx = {}) {
|
|
1003
950
|
return readUserWorkspaces(userCtx)
|
|
1004
951
|
.filter((entry) => entry.enabled !== false && entry.exists)
|
|
@@ -1351,6 +1298,12 @@ function isDisplayShareExpired(share) {
|
|
|
1351
1298
|
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
1352
1299
|
}
|
|
1353
1300
|
|
|
1301
|
+
function canReadDisplayShare(share, userCtx = {}) {
|
|
1302
|
+
if (!share) return false;
|
|
1303
|
+
if (String(share.visibility || "public") !== "private") return true;
|
|
1304
|
+
return userCtx?.isAdmin === true || String(share.userId || "") === String(userCtx?.userId || "");
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1354
1307
|
function getDisplayShareOrExpired(id) {
|
|
1355
1308
|
const shares = readDisplayShares();
|
|
1356
1309
|
const share = shares[id];
|
|
@@ -1376,10 +1329,78 @@ function displayShareSummary(share, baseUrl = "") {
|
|
|
1376
1329
|
expiresAt: String(share?.expiresAt || ""),
|
|
1377
1330
|
expiresMode: String(share?.expiresMode || (share?.expiresAt ? "days" : "permanent")),
|
|
1378
1331
|
expiresInDays: share?.expiresInDays == null ? null : Number(share.expiresInDays),
|
|
1332
|
+
visibility: String(share?.visibility || "public") === "private" ? "private" : "public",
|
|
1379
1333
|
url: displayShareOutputUrl(share?.id || "", baseUrl),
|
|
1380
1334
|
};
|
|
1381
1335
|
}
|
|
1382
1336
|
|
|
1337
|
+
function spacePublicSummary(space = {}, baseUrl = "") {
|
|
1338
|
+
const ownerId = String(space.ownerId || "");
|
|
1339
|
+
const slug = String(space.slug || "");
|
|
1340
|
+
const rootPath = `/s/${encodeURIComponent(ownerId)}/${encodeURIComponent(slug)}`;
|
|
1341
|
+
const base = normalizePublicBaseUrl(baseUrl);
|
|
1342
|
+
const absoluteRoot = base ? new URL(rootPath, base.endsWith("/") ? base : `${base}/`).href : rootPath;
|
|
1343
|
+
return {
|
|
1344
|
+
id: String(space.id || ""),
|
|
1345
|
+
ownerId,
|
|
1346
|
+
slug,
|
|
1347
|
+
title: String(space.title || slug),
|
|
1348
|
+
description: String(space.description || ""),
|
|
1349
|
+
visibility: normalizeSpaceVisibility(space.visibility),
|
|
1350
|
+
status: String(space.status || "active"),
|
|
1351
|
+
pages: (Array.isArray(space.pages) ? space.pages : []).map((page) => ({
|
|
1352
|
+
id: String(page.id || ""),
|
|
1353
|
+
title: String(page.title || ""),
|
|
1354
|
+
path: normalizeSpacePagePath(page.path || "/"),
|
|
1355
|
+
shareId: String(page.shareId || ""),
|
|
1356
|
+
hidden: page.hidden === true,
|
|
1357
|
+
order: Number(page.order || 0),
|
|
1358
|
+
})),
|
|
1359
|
+
url: absoluteRoot,
|
|
1360
|
+
createdAt: String(space.createdAt || ""),
|
|
1361
|
+
updatedAt: String(space.updatedAt || ""),
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function canManageSpace(space, userCtx = {}) {
|
|
1366
|
+
return Boolean(space) && (userCtx?.isAdmin === true || String(space.ownerId || "") === String(userCtx?.userId || ""));
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function displayShareSpaceReferences(shareId = "") {
|
|
1370
|
+
const wanted = String(shareId || "").trim();
|
|
1371
|
+
if (!wanted) return [];
|
|
1372
|
+
const refs = [];
|
|
1373
|
+
for (const space of readSpaces()) {
|
|
1374
|
+
for (const page of Array.isArray(space.pages) ? space.pages : []) {
|
|
1375
|
+
if (String(page.shareId || "") !== wanted) continue;
|
|
1376
|
+
refs.push({ spaceId: space.id, ownerId: space.ownerId, slug: space.slug, pageId: page.id, path: page.path });
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return refs;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function syncSpaceShareVisibility(space) {
|
|
1383
|
+
if (!space) return;
|
|
1384
|
+
const shares = readDisplayShares();
|
|
1385
|
+
let changed = false;
|
|
1386
|
+
for (const page of Array.isArray(space.pages) ? space.pages : []) {
|
|
1387
|
+
const share = shares[String(page.shareId || "")];
|
|
1388
|
+
if (!share || String(share.userId || "") !== String(space.ownerId || "")) continue;
|
|
1389
|
+
const visibility = normalizeSpaceVisibility(space.visibility);
|
|
1390
|
+
if (share.visibility === visibility && !share.expiresAt) continue;
|
|
1391
|
+
shares[page.shareId] = {
|
|
1392
|
+
...share,
|
|
1393
|
+
visibility,
|
|
1394
|
+
expiresAt: "",
|
|
1395
|
+
expiresMode: "permanent",
|
|
1396
|
+
expiresInDays: null,
|
|
1397
|
+
updatedAt: new Date().toISOString(),
|
|
1398
|
+
};
|
|
1399
|
+
changed = true;
|
|
1400
|
+
}
|
|
1401
|
+
if (changed) writeDisplayShares(shares);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1383
1404
|
function listDisplaySharesForUser(userCtx = {}, baseUrl = "") {
|
|
1384
1405
|
const userId = String(userCtx?.userId || "");
|
|
1385
1406
|
const isAdmin = userCtx?.isAdmin === true;
|
|
@@ -1407,6 +1428,9 @@ function updateDisplayShareExpiryForUser(id, userCtx = {}, patch = {}) {
|
|
|
1407
1428
|
const userId = String(userCtx?.userId || "");
|
|
1408
1429
|
if (userCtx?.isAdmin !== true && String(share.userId || "") !== userId) return { status: 403, error: "Forbidden" };
|
|
1409
1430
|
const expiry = normalizeDisplayShareExpiry(patch, new Date());
|
|
1431
|
+
if (displayShareSpaceReferences(id).length > 0 && expiry.expiresMode !== "permanent") {
|
|
1432
|
+
return { status: 409, error: "Space 页面使用中的展示内容必须永久有效;请先从空间移除页面" };
|
|
1433
|
+
}
|
|
1410
1434
|
const updated = {
|
|
1411
1435
|
...share,
|
|
1412
1436
|
expiresAt: expiry.expiresAt,
|
|
@@ -1425,6 +1449,9 @@ function deleteDisplayShareForUser(id, userCtx = {}) {
|
|
|
1425
1449
|
if (!share) return { status: 404, error: "Display share not found" };
|
|
1426
1450
|
const userId = String(userCtx?.userId || "");
|
|
1427
1451
|
if (userCtx?.isAdmin !== true && String(share.userId || "") !== userId) return { status: 403, error: "Forbidden" };
|
|
1452
|
+
if (displayShareSpaceReferences(id).length > 0) {
|
|
1453
|
+
return { status: 409, error: "展示内容仍被 Space 页面引用;请先从空间移除页面" };
|
|
1454
|
+
}
|
|
1428
1455
|
delete shares[id];
|
|
1429
1456
|
writeDisplayShares(shares);
|
|
1430
1457
|
return { status: 200 };
|
|
@@ -2339,6 +2366,154 @@ export function startUiServer({
|
|
|
2339
2366
|
json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
|
|
2340
2367
|
return;
|
|
2341
2368
|
}
|
|
2369
|
+
|
|
2370
|
+
if (req.method === "GET" && url.pathname === "/api/spaces/public") {
|
|
2371
|
+
try {
|
|
2372
|
+
const ownerId = String(url.searchParams.get("owner") || "").trim();
|
|
2373
|
+
const slug = String(url.searchParams.get("slug") || "").trim();
|
|
2374
|
+
const space = getSpaceByRoute(ownerId, slug);
|
|
2375
|
+
if (!space || (!canManageSpace(space, userCtx) && normalizeSpaceVisibility(space.visibility) === "private")) {
|
|
2376
|
+
json(res, 404, { error: "Space not found" });
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
if (space.status === "paused" && !canManageSpace(space, userCtx)) {
|
|
2380
|
+
json(res, 410, { error: "Space is paused" });
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
json(res, 200, {
|
|
2384
|
+
space: spacePublicSummary(space, requestPublicBaseUrl(req)),
|
|
2385
|
+
manageable: canManageSpace(space, userCtx),
|
|
2386
|
+
});
|
|
2387
|
+
} catch (e) {
|
|
2388
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
2389
|
+
}
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
if (req.method === "GET" && url.pathname === "/api/spaces") {
|
|
2394
|
+
if (!authUser?.userId) {
|
|
2395
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
const spaces = listSpacesForUser(userCtx.userId, userCtx.isAdmin)
|
|
2399
|
+
.map((space) => spacePublicSummary(space, requestPublicBaseUrl(req)));
|
|
2400
|
+
json(res, 200, { spaces });
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
if (req.method === "POST" && url.pathname === "/api/spaces") {
|
|
2405
|
+
if (!authUser?.userId) {
|
|
2406
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
try {
|
|
2410
|
+
const payload = JSON.parse(await readBody(req));
|
|
2411
|
+
const result = createSpace({
|
|
2412
|
+
ownerId: userCtx.userId,
|
|
2413
|
+
slug: payload.slug,
|
|
2414
|
+
title: payload.title,
|
|
2415
|
+
description: payload.description,
|
|
2416
|
+
visibility: payload.visibility,
|
|
2417
|
+
});
|
|
2418
|
+
if (result.error) {
|
|
2419
|
+
json(res, 400, { error: result.error });
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2423
|
+
} catch (e) {
|
|
2424
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2425
|
+
}
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
if (req.method === "PATCH" && url.pathname === "/api/spaces") {
|
|
2430
|
+
if (!authUser?.userId) {
|
|
2431
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
try {
|
|
2435
|
+
const payload = JSON.parse(await readBody(req));
|
|
2436
|
+
const current = getSpaceById(payload.id);
|
|
2437
|
+
if (!canManageSpace(current, userCtx)) {
|
|
2438
|
+
json(res, current ? 403 : 404, { error: current ? "Forbidden" : "Space not found" });
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
const result = updateSpace(payload.id, payload);
|
|
2442
|
+
if (result.error) {
|
|
2443
|
+
json(res, 400, { error: result.error });
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
syncSpaceShareVisibility(result.space);
|
|
2447
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2448
|
+
} catch (e) {
|
|
2449
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2450
|
+
}
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
if (req.method === "POST" && url.pathname === "/api/spaces/page") {
|
|
2455
|
+
if (!authUser?.userId) {
|
|
2456
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
try {
|
|
2460
|
+
const payload = JSON.parse(await readBody(req));
|
|
2461
|
+
const space = getSpaceById(payload.spaceId);
|
|
2462
|
+
if (!canManageSpace(space, userCtx)) {
|
|
2463
|
+
json(res, space ? 403 : 404, { error: space ? "Forbidden" : "Space not found" });
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const { share } = getDisplayShareOrExpired(String(payload.shareId || "").trim());
|
|
2467
|
+
if (!share || (userCtx.isAdmin !== true && String(share.userId || "") !== String(userCtx.userId || ""))) {
|
|
2468
|
+
json(res, 400, { error: "Display share not found or not owned by current user" });
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
const foreignSpaceRef = displayShareSpaceReferences(share.id).find((ref) => ref.spaceId !== space.id);
|
|
2472
|
+
if (foreignSpaceRef) {
|
|
2473
|
+
json(res, 409, { error: "同一展示内容不能绑定到多个空间;请从 Workspace 重新生成一次展示" });
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const result = upsertSpacePage(space.id, {
|
|
2477
|
+
title: payload.title,
|
|
2478
|
+
path: payload.path,
|
|
2479
|
+
shareId: share.id,
|
|
2480
|
+
hidden: payload.hidden === true,
|
|
2481
|
+
});
|
|
2482
|
+
if (result.error) {
|
|
2483
|
+
json(res, 400, { error: result.error });
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
syncSpaceShareVisibility(result.space);
|
|
2487
|
+
json(res, 200, {
|
|
2488
|
+
ok: true,
|
|
2489
|
+
page: result.page,
|
|
2490
|
+
space: spacePublicSummary(result.space, requestPublicBaseUrl(req)),
|
|
2491
|
+
});
|
|
2492
|
+
} catch (e) {
|
|
2493
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
2494
|
+
}
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
if (req.method === "DELETE" && url.pathname === "/api/spaces/page") {
|
|
2499
|
+
if (!authUser?.userId) {
|
|
2500
|
+
json(res, 401, { error: "Unauthorized" });
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const space = getSpaceById(url.searchParams.get("spaceId") || "");
|
|
2504
|
+
if (!canManageSpace(space, userCtx)) {
|
|
2505
|
+
json(res, space ? 403 : 404, { error: space ? "Forbidden" : "Space not found" });
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
const result = deleteSpacePage(space.id, url.searchParams.get("pageId") || "");
|
|
2509
|
+
if (result.error) {
|
|
2510
|
+
json(res, 404, { error: result.error });
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
json(res, 200, { ok: true, space: spacePublicSummary(result.space, requestPublicBaseUrl(req)) });
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2342
2517
|
if (req.method === "GET" && url.pathname === "/api/display/shares") {
|
|
2343
2518
|
try {
|
|
2344
2519
|
if (!authUser?.userId) {
|
|
@@ -2366,6 +2541,10 @@ export function startUiServer({
|
|
|
2366
2541
|
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
2367
2542
|
return;
|
|
2368
2543
|
}
|
|
2544
|
+
if (!canReadDisplayShare(share, userCtx)) {
|
|
2545
|
+
json(res, 404, { error: "Display share not found" });
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2369
2548
|
const payload = publicDisplayPayloadFromShare(root, share);
|
|
2370
2549
|
if (payload.error) {
|
|
2371
2550
|
json(res, 404, { error: payload.error });
|
|
@@ -2446,6 +2625,10 @@ export function startUiServer({
|
|
|
2446
2625
|
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
2447
2626
|
return;
|
|
2448
2627
|
}
|
|
2628
|
+
if (!canReadDisplayShare(share, userCtx)) {
|
|
2629
|
+
json(res, 404, { error: "Display share not found" });
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2449
2632
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
2450
2633
|
flowId: share.flowId || "",
|
|
2451
2634
|
flowSource: share.flowSource || "user",
|
|
@@ -3096,6 +3279,7 @@ export function startUiServer({
|
|
|
3096
3279
|
expiresInDays: payload.expiresInDays,
|
|
3097
3280
|
permanent: payload.permanent,
|
|
3098
3281
|
expiresAt: payload.expiresAt,
|
|
3282
|
+
visibility: payload.visibility,
|
|
3099
3283
|
});
|
|
3100
3284
|
json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(share.id)}` });
|
|
3101
3285
|
} catch (e) {
|
|
@@ -17,12 +17,21 @@ import { buildSkillCompactInjectionBlock, loadResourcesForSkillKeys } from "./co
|
|
|
17
17
|
import { execFileBuffered } from "./exec-buffered.mjs";
|
|
18
18
|
import { runGit } from "./git-worktree.mjs";
|
|
19
19
|
import {
|
|
20
|
+
listMarketplaceFlows,
|
|
20
21
|
listMarketplacePackages,
|
|
21
22
|
nodePackageArchive,
|
|
23
|
+
publishMarketplaceFlow,
|
|
22
24
|
publishNodePackage,
|
|
23
25
|
publishNodePackageArchive,
|
|
26
|
+
readMarketplaceFlow,
|
|
24
27
|
resolveMarketplaceNodePackage,
|
|
28
|
+
setMarketplaceVisibility,
|
|
25
29
|
} from "./marketplace.mjs";
|
|
30
|
+
import {
|
|
31
|
+
appendMarketplaceUsageEvent,
|
|
32
|
+
marketplaceResourcesForRun,
|
|
33
|
+
writeMarketplaceFlowOrigin,
|
|
34
|
+
} from "./marketplace-usage.mjs";
|
|
26
35
|
import { NODE_PACKAGE_ENTRY, nodePackageExportsRun, readNodePackageManifest } from "./node-package-manifest.mjs";
|
|
27
36
|
import { inspectNodePackageDirectory } from "./node-package-archive.mjs";
|
|
28
37
|
import { json, readBody } from "./http-util.mjs";
|
|
@@ -38,8 +47,8 @@ import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision
|
|
|
38
47
|
import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
|
|
39
48
|
import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
|
|
40
49
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
|
|
41
|
-
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, 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";
|
|
42
|
-
import { WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
50
|
+
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, 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";
|
|
51
|
+
import { splitWorkspaceGraph, WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
43
52
|
import { getWorkspaceTree } from "./workspace-tree.mjs";
|
|
44
53
|
import busboy from "busboy";
|
|
45
54
|
import crypto from "crypto";
|
|
@@ -921,6 +930,11 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
921
930
|
inputs: node.inputs,
|
|
922
931
|
outputs: node.outputs,
|
|
923
932
|
ownerUserId: node.ownerUserId || node.createdBy || "",
|
|
933
|
+
visibility: node.visibility || "public",
|
|
934
|
+
useCount: Number(node.useCount || 0),
|
|
935
|
+
installCount: Number(node.installCount || 0),
|
|
936
|
+
uniqueUserCount: Number(node.uniqueUserCount || 0),
|
|
937
|
+
lastUsedAt: node.lastUsedAt || "",
|
|
924
938
|
fileList: node.fileList || [],
|
|
925
939
|
fileCount: node.fileCount || 0,
|
|
926
940
|
totalBytes: node.totalBytes || 0,
|
|
@@ -980,6 +994,14 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
980
994
|
json(res, 404, { error: result.error || "Node package not found" });
|
|
981
995
|
return;
|
|
982
996
|
}
|
|
997
|
+
appendMarketplaceUsageEvent(root, {
|
|
998
|
+
kind: "node",
|
|
999
|
+
id,
|
|
1000
|
+
version,
|
|
1001
|
+
action: "install",
|
|
1002
|
+
actorUserId: userCtx.userId || "",
|
|
1003
|
+
eventId: `install:node:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1004
|
+
});
|
|
983
1005
|
res.writeHead(200, {
|
|
984
1006
|
"Content-Type": "application/zip",
|
|
985
1007
|
"Content-Length": result.archive.length,
|
|
@@ -995,6 +1017,158 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
995
1017
|
return;
|
|
996
1018
|
}
|
|
997
1019
|
|
|
1020
|
+
if (req.method === "GET" && url.pathname === "/api/marketplace/resources") {
|
|
1021
|
+
try {
|
|
1022
|
+
const kind = String(url.searchParams.get("kind") || "flow").trim();
|
|
1023
|
+
const marketplaceScope = url.searchParams.get("scope") === "owned" ? "owned" : "all";
|
|
1024
|
+
const queryText = String(url.searchParams.get("q") || "").trim().toLowerCase();
|
|
1025
|
+
const matches = (item) => !queryText || [
|
|
1026
|
+
item.id,
|
|
1027
|
+
item.displayName,
|
|
1028
|
+
item.description,
|
|
1029
|
+
item.ownerUserId,
|
|
1030
|
+
...(Array.isArray(item.tags) ? item.tags : []),
|
|
1031
|
+
].filter(Boolean).join("\n").toLowerCase().includes(queryText);
|
|
1032
|
+
if (kind === "node") {
|
|
1033
|
+
const nodes = listMarketplacePackages(root, { ...userCtx, marketplaceScope }).nodes.filter(matches);
|
|
1034
|
+
json(res, 200, { kind, sort: "useCount", order: "desc", items: nodes });
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (kind !== "flow") {
|
|
1038
|
+
json(res, 400, { error: "kind must be flow or node" });
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const flows = listMarketplaceFlows(root, { ...userCtx, marketplaceScope }).flows.filter(matches);
|
|
1042
|
+
json(res, 200, { kind, sort: "useCount", order: "desc", items: flows });
|
|
1043
|
+
} catch (e) {
|
|
1044
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1045
|
+
}
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/publish") {
|
|
1050
|
+
let payload;
|
|
1051
|
+
try {
|
|
1052
|
+
payload = JSON.parse(await readBody(req));
|
|
1053
|
+
} catch {
|
|
1054
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
try {
|
|
1058
|
+
const flowId = String(payload?.flowId || "").trim();
|
|
1059
|
+
const flowSource = String(payload?.flowSource || "user").trim();
|
|
1060
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
1061
|
+
flowId,
|
|
1062
|
+
flowSource,
|
|
1063
|
+
archived: payload?.archived === true,
|
|
1064
|
+
}, userCtx);
|
|
1065
|
+
if (scoped.error) {
|
|
1066
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
1070
|
+
json(res, 400, { error: "Only active editable flows can be published" });
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const current = readWorkspaceGraph(scoped.root, root).graph;
|
|
1074
|
+
const { design } = splitWorkspaceGraph(current);
|
|
1075
|
+
const graph = workspaceGraphWithScheduleMode(design, "disabled");
|
|
1076
|
+
const result = publishMarketplaceFlow(root, {
|
|
1077
|
+
id: payload?.id || flowId,
|
|
1078
|
+
version: payload?.version || "1.0.0",
|
|
1079
|
+
displayName: payload?.displayName || flowId,
|
|
1080
|
+
description: payload?.description || "",
|
|
1081
|
+
tags: payload?.tags || [],
|
|
1082
|
+
visibility: payload?.visibility || "public",
|
|
1083
|
+
graph,
|
|
1084
|
+
}, userCtx);
|
|
1085
|
+
json(res, result.ok ? (result.alreadyExists ? 200 : 201) : (result.conflict ? 409 : 400), result);
|
|
1086
|
+
} catch (e) {
|
|
1087
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1088
|
+
}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/install") {
|
|
1093
|
+
let payload;
|
|
1094
|
+
try {
|
|
1095
|
+
payload = JSON.parse(await readBody(req));
|
|
1096
|
+
} catch {
|
|
1097
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const id = String(payload?.id || "").trim();
|
|
1101
|
+
const version = String(payload?.version || "").trim();
|
|
1102
|
+
const idCheck = validateUserPipelineId(String(payload?.flowId || id));
|
|
1103
|
+
if (!id || !version || !idCheck.ok) {
|
|
1104
|
+
json(res, 400, { error: idCheck.error || "Missing marketplace flow id or version" });
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const flowId = idCheck.flowId;
|
|
1108
|
+
const targetDir = path.join(getUserPipelinesRoot(userCtx.userId), flowId);
|
|
1109
|
+
if (fs.existsSync(targetDir)) {
|
|
1110
|
+
json(res, 409, { error: `已存在同名流水线 ${flowId}` });
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
|
|
1115
|
+
if (!source.ok) {
|
|
1116
|
+
json(res, 404, { error: source.error || "Marketplace flow not found" });
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1120
|
+
const graph = workspaceGraphWithScheduleMode(source.graph, "disabled");
|
|
1121
|
+
writeWorkspaceGraph(targetDir, graph, root);
|
|
1122
|
+
writeMarketplaceFlowOrigin(targetDir, { id, version });
|
|
1123
|
+
appendMarketplaceUsageEvent(root, {
|
|
1124
|
+
kind: "flow",
|
|
1125
|
+
id,
|
|
1126
|
+
version,
|
|
1127
|
+
action: "install",
|
|
1128
|
+
actorUserId: userCtx.userId || "",
|
|
1129
|
+
eventId: `install:flow:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1130
|
+
});
|
|
1131
|
+
json(res, 201, {
|
|
1132
|
+
ok: true,
|
|
1133
|
+
id,
|
|
1134
|
+
version,
|
|
1135
|
+
flowId,
|
|
1136
|
+
flowSource: "user",
|
|
1137
|
+
url: `/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=user`,
|
|
1138
|
+
});
|
|
1139
|
+
} catch (e) {
|
|
1140
|
+
try {
|
|
1141
|
+
if (fs.existsSync(targetDir)) fs.rmSync(targetDir, { recursive: true, force: true });
|
|
1142
|
+
} catch {}
|
|
1143
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1144
|
+
}
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (req.method === "PATCH" && url.pathname === "/api/marketplace/visibility") {
|
|
1149
|
+
let payload;
|
|
1150
|
+
try {
|
|
1151
|
+
payload = JSON.parse(await readBody(req));
|
|
1152
|
+
} catch {
|
|
1153
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
try {
|
|
1157
|
+
const result = setMarketplaceVisibility(
|
|
1158
|
+
root,
|
|
1159
|
+
String(payload?.kind || ""),
|
|
1160
|
+
String(payload?.id || ""),
|
|
1161
|
+
String(payload?.version || ""),
|
|
1162
|
+
String(payload?.visibility || "public"),
|
|
1163
|
+
userCtx,
|
|
1164
|
+
);
|
|
1165
|
+
json(res, result.ok ? 200 : 400, result);
|
|
1166
|
+
} catch (e) {
|
|
1167
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1168
|
+
}
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
998
1172
|
if (req.method === "GET" && url.pathname === "/api/workspace-tree") {
|
|
999
1173
|
try {
|
|
1000
1174
|
json(res, 200, getWorkspaceTree(root));
|
|
@@ -2158,6 +2332,8 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2158
2332
|
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
2159
2333
|
plannedNodeIds,
|
|
2160
2334
|
startedAt: Date.now(),
|
|
2335
|
+
workspaceRoot: root,
|
|
2336
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, runtimeGraph, plannedNodeIds),
|
|
2161
2337
|
};
|
|
2162
2338
|
const runLog = createWorkspaceRunLogSession({
|
|
2163
2339
|
runId,
|
|
@@ -2255,6 +2431,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2255
2431
|
...runEntry,
|
|
2256
2432
|
endedAt,
|
|
2257
2433
|
durationMs: endedAt - runEntry.startedAt,
|
|
2434
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, runtimeGraph, result.order || Array.from(touchedIds)),
|
|
2258
2435
|
}, "success");
|
|
2259
2436
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
2260
2437
|
endedAt,
|
|
@@ -2350,6 +2527,7 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2350
2527
|
...runEntry,
|
|
2351
2528
|
endedAt,
|
|
2352
2529
|
durationMs: endedAt - runEntry.startedAt,
|
|
2530
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, runtimeGraph, result.order || Array.from(touchedIds)),
|
|
2353
2531
|
}, "success");
|
|
2354
2532
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
2355
2533
|
endedAt,
|
|
@@ -2574,6 +2752,11 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2574
2752
|
return;
|
|
2575
2753
|
}
|
|
2576
2754
|
removeWorkspaceDeferredRun(deferred.key);
|
|
2755
|
+
const resourceCleanup = cleanupWorkspaceRunResources(scoped.root, deferred.runId, {
|
|
2756
|
+
force: false,
|
|
2757
|
+
status: "stopped",
|
|
2758
|
+
emit: (event) => appendWorkspaceRunLogEvent(deferred.runId, event),
|
|
2759
|
+
});
|
|
2577
2760
|
const endedAt = Date.now();
|
|
2578
2761
|
appendWorkspaceRunLogEvent(deferred.runId, {
|
|
2579
2762
|
type: "stop-completed",
|
|
@@ -2602,6 +2785,10 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
2602
2785
|
ok: true,
|
|
2603
2786
|
stopped: true,
|
|
2604
2787
|
monitoringOnly: true,
|
|
2788
|
+
resources: {
|
|
2789
|
+
cleaned: resourceCleanup.cleaned,
|
|
2790
|
+
preserved: resourceCleanup.preserved,
|
|
2791
|
+
},
|
|
2605
2792
|
});
|
|
2606
2793
|
return;
|
|
2607
2794
|
}
|