@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
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
publishFlowSnippet,
|
|
68
68
|
publishNodeFromInstance,
|
|
69
69
|
} from "./marketplace.mjs";
|
|
70
|
+
import { markRepositoryIndexDirty } from "./repository-index.mjs";
|
|
70
71
|
import { runGit } from "./git-worktree.mjs";
|
|
71
72
|
import {
|
|
72
73
|
authSetupRequired,
|
|
@@ -93,6 +94,13 @@ import {
|
|
|
93
94
|
renderCliAuthorizationResult,
|
|
94
95
|
} from "./cli-auth-page.mjs";
|
|
95
96
|
import { readGlobalEnvRows, readUserEnvRows, writeGlobalEnvRows, writeUserEnvRows } from "./user-env.mjs";
|
|
97
|
+
import { runCursorAgentWithPrompt } from "./agent-runners.mjs";
|
|
98
|
+
import {
|
|
99
|
+
clearCursorApiKeyCooldown,
|
|
100
|
+
getCursorApiKeyModelSelection,
|
|
101
|
+
getCursorApiKeyPoolStatuses,
|
|
102
|
+
parseCursorApiKeyRecords,
|
|
103
|
+
} from "./cursor-api-key-pool.mjs";
|
|
96
104
|
import {
|
|
97
105
|
readAdminBuiltinPipelineConfig,
|
|
98
106
|
updateAdminBuiltinPipelineConfig,
|
|
@@ -208,8 +216,84 @@ const MIME = {
|
|
|
208
216
|
const ADMIN_ONLY_USER_ENV_KEYS = new Set([
|
|
209
217
|
"CURSOR_API_KEYS",
|
|
210
218
|
"AGENTFLOW_CURSOR_API_KEY_COOLDOWN_MINUTES",
|
|
219
|
+
"AGENTFLOW_CURSOR_API_KEY_RESOURCE_EXHAUSTED_COOLDOWN_MINUTES",
|
|
211
220
|
"CURSOR_API_KEY_COOLDOWN_MINUTES",
|
|
212
221
|
]);
|
|
222
|
+
const activeCursorApiKeyTests = new Set();
|
|
223
|
+
|
|
224
|
+
function cursorApiKeyRowsForScope(userCtx = {}, scope = "global") {
|
|
225
|
+
const rows = scope === "user" ? readUserEnvRows(userCtx.userId) : readGlobalEnvRows();
|
|
226
|
+
const poolRow = rows.find((row) => String(row?.key || "").trim() === "CURSOR_API_KEYS");
|
|
227
|
+
return parseCursorApiKeyRecords(poolRow?.value || "");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function maskCursorApiKeyForAdmin(key) {
|
|
231
|
+
const value = String(key || "").trim();
|
|
232
|
+
if (!value) return "";
|
|
233
|
+
if (value.length <= 10) return `${"•".repeat(Math.max(0, value.length - 4))}${value.slice(-4)}`;
|
|
234
|
+
return `${value.slice(0, 4)}${"•".repeat(Math.min(18, value.length - 8))}${value.slice(-4)}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function cursorApiKeyAdminMetadata(userCtx = {}, scope = "global") {
|
|
238
|
+
const records = cursorApiKeyRowsForScope(userCtx, scope);
|
|
239
|
+
const statuses = new Map(getCursorApiKeyPoolStatuses(records).map((item) => [item.id, item]));
|
|
240
|
+
return records.map((record) => ({
|
|
241
|
+
id: record.id,
|
|
242
|
+
name: record.name,
|
|
243
|
+
maskedKey: maskCursorApiKeyForAdmin(record.key),
|
|
244
|
+
createdAt: record.createdAt || "",
|
|
245
|
+
...(statuses.get(record.id) || {}),
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function probeCursorApiKey(root, record) {
|
|
250
|
+
const startedAt = Date.now();
|
|
251
|
+
const modelSelection = getCursorApiKeyModelSelection(record.id) || { lane: "auto", modelId: "auto", modelName: "Auto" };
|
|
252
|
+
const selection = { ...record, index: 0, total: 1, modelSelection };
|
|
253
|
+
let replyPreview = "";
|
|
254
|
+
let timedOut = false;
|
|
255
|
+
const handle = runCursorAgentWithPrompt(root, "你好。这是连通性测试,请不要调用任何工具,只回复“你好”。", {
|
|
256
|
+
env: { CURSOR_API_KEYS: JSON.stringify([record]), AGENTFLOW_USER_ID: "" },
|
|
257
|
+
_agentflowCursorApiKeyAttempts: [selection],
|
|
258
|
+
mode: "ask",
|
|
259
|
+
force: false,
|
|
260
|
+
sandboxDisabled: false,
|
|
261
|
+
approveMcps: false,
|
|
262
|
+
onStreamEvent(event) {
|
|
263
|
+
if (event?.type !== "natural" || !event?.text) return;
|
|
264
|
+
if (event.kind === "result" || !replyPreview) replyPreview = String(event.text).trim().slice(0, 500);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
const timeout = setTimeout(() => {
|
|
268
|
+
timedOut = true;
|
|
269
|
+
try { handle.child.kill("SIGTERM"); } catch (_) {}
|
|
270
|
+
}, 30_000);
|
|
271
|
+
try {
|
|
272
|
+
await handle.finished;
|
|
273
|
+
if (timedOut) throw new Error("Cursor API Key 测试超时(30 秒)");
|
|
274
|
+
return {
|
|
275
|
+
success: true,
|
|
276
|
+
testedAt: new Date().toISOString(),
|
|
277
|
+
durationMs: Date.now() - startedAt,
|
|
278
|
+
replyPreview,
|
|
279
|
+
modelId: modelSelection.modelId,
|
|
280
|
+
modelName: modelSelection.modelName,
|
|
281
|
+
modelLane: modelSelection.lane,
|
|
282
|
+
};
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
testedAt: new Date().toISOString(),
|
|
287
|
+
durationMs: Date.now() - startedAt,
|
|
288
|
+
errorPreview: timedOut ? "Cursor API Key 测试超时(30 秒)" : String(error?.message || error).slice(0, 500),
|
|
289
|
+
modelId: modelSelection.modelId,
|
|
290
|
+
modelName: modelSelection.modelName,
|
|
291
|
+
modelLane: modelSelection.lane,
|
|
292
|
+
};
|
|
293
|
+
} finally {
|
|
294
|
+
clearTimeout(timeout);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
213
297
|
|
|
214
298
|
const UI_SERVER_STARTED_AT = new Date().toISOString();
|
|
215
299
|
const UI_SERVER_APP_VERSION = (() => {
|
|
@@ -2183,6 +2267,9 @@ export function startUiServer({
|
|
|
2183
2267
|
for (const item of summary.needsDecision) log.warn(`[storage-migration] needs decision: ${item}`);
|
|
2184
2268
|
for (const item of summary.failed) log.warn(`[storage-migration] failed: ${item}`);
|
|
2185
2269
|
}
|
|
2270
|
+
// Repository discovery is derived data. Reconcile it off the request path so a missing or stale
|
|
2271
|
+
// index delays startup work, not the first person opening the repository page.
|
|
2272
|
+
markRepositoryIndexDirty(root);
|
|
2186
2273
|
const server = http.createServer(async (req, res) => {
|
|
2187
2274
|
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
2188
2275
|
const reqStart = Date.now();
|
|
@@ -3680,6 +3767,84 @@ export function startUiServer({
|
|
|
3680
3767
|
return;
|
|
3681
3768
|
}
|
|
3682
3769
|
|
|
3770
|
+
if (req.method === "GET" && url.pathname === "/api/admin/cursor-api-keys/status") {
|
|
3771
|
+
if (!authUser?.isAdmin) {
|
|
3772
|
+
json(res, 403, { error: "Admin permission required" });
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
3775
|
+
try {
|
|
3776
|
+
const scope = url.searchParams.get("scope") === "user" ? "user" : "global";
|
|
3777
|
+
json(res, 200, { scope, keys: cursorApiKeyAdminMetadata(userCtx, scope) });
|
|
3778
|
+
} catch (e) {
|
|
3779
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
3780
|
+
}
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
if (req.method === "PATCH" && url.pathname === "/api/admin/cursor-api-keys/cooldown") {
|
|
3785
|
+
if (!authUser?.isAdmin) {
|
|
3786
|
+
json(res, 403, { error: "Admin permission required" });
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
let payload;
|
|
3790
|
+
try {
|
|
3791
|
+
payload = JSON.parse(await readBody(req));
|
|
3792
|
+
} catch {
|
|
3793
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
const scope = payload?.scope === "user" ? "user" : "global";
|
|
3797
|
+
const id = String(payload?.id || "").trim();
|
|
3798
|
+
const record = cursorApiKeyRowsForScope(userCtx, scope).find((item) => item.id === id);
|
|
3799
|
+
if (!record) {
|
|
3800
|
+
json(res, 404, { error: "Cursor API Key not found" });
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
clearCursorApiKeyCooldown(record);
|
|
3804
|
+
json(res, 200, { success: true, scope, keys: cursorApiKeyAdminMetadata(userCtx, scope) });
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
if (req.method === "POST" && url.pathname === "/api/admin/cursor-api-keys/test") {
|
|
3809
|
+
if (!authUser?.isAdmin) {
|
|
3810
|
+
json(res, 403, { error: "Admin permission required" });
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3813
|
+
let payload;
|
|
3814
|
+
try {
|
|
3815
|
+
payload = JSON.parse(await readBody(req));
|
|
3816
|
+
} catch {
|
|
3817
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
const scope = payload?.scope === "user" ? "user" : "global";
|
|
3821
|
+
const id = String(payload?.id || "").trim();
|
|
3822
|
+
const record = cursorApiKeyRowsForScope(userCtx, scope).find((item) => item.id === id);
|
|
3823
|
+
if (!record) {
|
|
3824
|
+
json(res, 404, { error: "Cursor API Key not found" });
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
const testId = `${scope}:${record.id}`;
|
|
3828
|
+
if (activeCursorApiKeyTests.has(testId)) {
|
|
3829
|
+
json(res, 409, { error: "该 Cursor API Key 正在测试中" });
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
activeCursorApiKeyTests.add(testId);
|
|
3833
|
+
try {
|
|
3834
|
+
const result = await probeCursorApiKey(root, record);
|
|
3835
|
+
json(res, result.success ? 200 : 502, {
|
|
3836
|
+
result,
|
|
3837
|
+
scope,
|
|
3838
|
+
keys: cursorApiKeyAdminMetadata(userCtx, scope),
|
|
3839
|
+
});
|
|
3840
|
+
} catch (e) {
|
|
3841
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
3842
|
+
} finally {
|
|
3843
|
+
activeCursorApiKeyTests.delete(testId);
|
|
3844
|
+
}
|
|
3845
|
+
return;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3683
3848
|
if (req.method === "POST" && url.pathname === "/api/user-env") {
|
|
3684
3849
|
let payload;
|
|
3685
3850
|
try {
|
|
@@ -3919,6 +4084,7 @@ export function startUiServer({
|
|
|
3919
4084
|
}
|
|
3920
4085
|
try {
|
|
3921
4086
|
const result = deleteMarketplaceNodePackage(root, id, version, userCtx);
|
|
4087
|
+
if (result.ok) markRepositoryIndexDirty(root);
|
|
3922
4088
|
json(res, result.ok ? 200 : 400, result);
|
|
3923
4089
|
} catch (e) {
|
|
3924
4090
|
json(res, 500, { ok: false, error: (e && e.message) || String(e) });
|
|
@@ -3959,6 +4125,7 @@ export function startUiServer({
|
|
|
3959
4125
|
if (!resolved.error && resolved.flowDir) flowDir = resolved.flowDir;
|
|
3960
4126
|
}
|
|
3961
4127
|
const result = publishNodeFromInstance(root, payload || {}, { flowDir, ...userCtx });
|
|
4128
|
+
if (result.ok) markRepositoryIndexDirty(root);
|
|
3962
4129
|
json(res, result.ok ? 200 : 400, result);
|
|
3963
4130
|
} catch (e) {
|
|
3964
4131
|
json(res, 500, { ok: false, error: (e && e.message) || String(e) });
|