@fieldwangai/agentflow 0.1.166 → 0.1.168

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -319,6 +319,11 @@ function runCursorAgentWithPrivateMcp(cliWorkspace, prompt, options, userId) {
319
319
  * @param {string} [opts.modelKey]
320
320
  * @param {Record<string, string>} [opts.extraEnv]
321
321
  * @param {boolean} [opts.force]
322
+ * @param {"ask" | "plan"} [opts.mode]
323
+ * @param {boolean} [opts.sandboxDisabled]
324
+ * @param {boolean} [opts.approveMcps]
325
+ * @param {"read-only" | "workspace-write" | "danger-full-access"} [opts.sandboxMode]
326
+ * @param {boolean} [opts.includeJsonResult]
322
327
  * @param {(ev: object) => void} [opts.onStreamEvent]
323
328
  * @param {(subtype: string, toolName: string) => void} [opts.onToolCall]
324
329
  * @returns {{ child: import('child_process').ChildProcess, finished: Promise<void> }}
@@ -343,6 +348,12 @@ export function startComposerAgent(opts) {
343
348
  onChild: opts.onChild,
344
349
  detached: Boolean(opts.detached),
345
350
  force: Boolean(opts.force),
351
+ ...(opts.mode ? { mode: String(opts.mode) } : {}),
352
+ ...(opts.sandboxDisabled === false ? { sandboxDisabled: false } : {}),
353
+ ...(typeof opts.approveMcps === "boolean" ? { approveMcps: opts.approveMcps } : {}),
354
+ ...(opts.sandboxMode ? { sandboxMode: String(opts.sandboxMode) } : {}),
355
+ ...(opts.allowDanger === false ? { allowDanger: false } : {}),
356
+ ...(opts.includeJsonResult === true ? { includeJsonResult: true } : {}),
346
357
  env,
347
358
  addDirs: Array.isArray(opts.writableDirs)
348
359
  ? opts.writableDirs.map((dir) => String(dir || "").trim()).filter(Boolean)
@@ -18,6 +18,7 @@ export function parseCursorApiKeyRecords(value = "") {
18
18
  id: String(item.id || "").trim() || legacyKeyId(key),
19
19
  name: String(item.name || "").trim() || `Key ${index + 1}`,
20
20
  key,
21
+ createdAt: String(item.createdAt || "").trim(),
21
22
  };
22
23
  };
23
24
  try {
@@ -89,6 +90,7 @@ export function markCursorApiKeyLaneBlocked(
89
90
  cooldownMinutes = 30,
90
91
  errorText = "",
91
92
  now = Date.now(),
93
+ evidence = {},
92
94
  ) {
93
95
  const keyId = selectionId(keyOrSelection);
94
96
  if (!keyId || !["auto", "fallback"].includes(lane)) return 0;
@@ -97,12 +99,23 @@ export function markCursorApiKeyLaneBlocked(
97
99
  const currentLane = keyState[lane] || {};
98
100
  const blockedUntil = Math.max(currentLane.blockedUntil || 0, now + minutes * 60 * 1000);
99
101
  const errorCategory = classifyCursorApiKeyLimitError(errorText);
102
+ const fallbackModel = keyState.fallbackModel;
103
+ const lastFailure = {
104
+ triggeredAt: new Date(now).toISOString(),
105
+ ...(errorCategory ? { errorCategory } : {}),
106
+ errorPreview: sanitizeErrorPreview(errorText),
107
+ lane,
108
+ modelId: String(evidence?.modelId || (lane === "auto" ? "auto" : fallbackModel?.id || "fallback")),
109
+ modelName: String(evidence?.modelName || (lane === "auto" ? "Auto" : fallbackModel?.displayName || "降级模型")),
110
+ };
100
111
  keyState[lane] = {
101
112
  ...currentLane,
102
113
  blockedUntil,
103
114
  ...(errorCategory ? { errorCategory } : {}),
104
115
  ...(lane === "auto" ? { fallbackEligible: isCursorAutoFallbackEligible(errorText) } : {}),
116
+ lastFailure,
105
117
  };
118
+ keyState.lastFailure = lastFailure;
106
119
  cursorApiKeyStates.set(keyId, keyState);
107
120
  return blockedUntil;
108
121
  }
@@ -116,6 +129,72 @@ export function clearCursorApiKeyLaneCooldown(keyOrSelection, lane) {
116
129
  return true;
117
130
  }
118
131
 
132
+ export function clearCursorApiKeyCooldown(keyOrSelection) {
133
+ const keyState = cursorApiKeyStates.get(selectionId(keyOrSelection));
134
+ if (!keyState) return false;
135
+ let changed = false;
136
+ for (const lane of ["auto", "fallback"]) {
137
+ if (!keyState[lane] || (keyState[lane].blockedUntil || 0) <= 0) continue;
138
+ keyState[lane].blockedUntil = 0;
139
+ delete keyState[lane].errorCategory;
140
+ delete keyState[lane].fallbackEligible;
141
+ changed = true;
142
+ }
143
+ return changed;
144
+ }
145
+
146
+ export function recordCursorApiKeyUsage(keyOrSelection, selection = {}, now = Date.now()) {
147
+ const keyId = selectionId(keyOrSelection);
148
+ if (!keyId || keyId === "default") return;
149
+ const keyState = cursorApiKeyStates.get(keyId) || {};
150
+ keyState.lastUsedAt = new Date(now).toISOString();
151
+ keyState.lastSelection = {
152
+ lane: selection?.lane === "fallback" ? "fallback" : "auto",
153
+ modelId: String(selection?.modelId || "auto"),
154
+ modelName: String(selection?.modelName || "Auto"),
155
+ };
156
+ cursorApiKeyStates.set(keyId, keyState);
157
+ }
158
+
159
+ export function getCursorApiKeyPoolStatuses(records = [], now = Date.now()) {
160
+ return (Array.isArray(records) ? records : []).map((record) => {
161
+ const id = selectionId(record);
162
+ const keyState = cursorApiKeyStates.get(id);
163
+ const selection = getCursorApiKeyModelSelection(id, now);
164
+ const laneCooldowns = buildLaneCooldowns(keyState, now);
165
+ const common = {
166
+ id,
167
+ ...(keyState?.lastUsedAt ? { lastUsedAt: keyState.lastUsedAt } : {}),
168
+ ...(keyState?.fallbackModel ? { fallbackModel: keyState.fallbackModel } : {}),
169
+ laneCooldowns,
170
+ ...(keyState?.lastFailure ? { lastFailure: keyState.lastFailure } : {}),
171
+ };
172
+ if (selection) {
173
+ return {
174
+ ...common,
175
+ status: "available",
176
+ activeLane: selection.lane,
177
+ activeModelId: selection.modelId,
178
+ activeModelName: selection.modelName,
179
+ degraded: selection.lane === "fallback",
180
+ };
181
+ }
182
+ const activeCooldowns = laneCooldowns.filter((item) => item.remainingSeconds > 0);
183
+ const earliest = activeCooldowns.reduce(
184
+ (result, item) => !result || item.remainingSeconds < result.remainingSeconds ? item : result,
185
+ undefined,
186
+ );
187
+ const autoState = keyState?.auto;
188
+ return {
189
+ ...common,
190
+ status: "cooling_down",
191
+ ...(autoState?.errorCategory ? { errorCategory: autoState.errorCategory } : {}),
192
+ blockedUntil: earliest?.blockedUntil || new Date(Math.max(now, autoState?.blockedUntil || now)).toISOString(),
193
+ remainingSeconds: earliest?.remainingSeconds || Math.max(0, Math.ceil(((autoState?.blockedUntil || now) - now) / 1000)),
194
+ };
195
+ });
196
+ }
197
+
119
198
  export function cursorApiKeyEnv(selection) {
120
199
  return selection?.key ? { CURSOR_API_KEY: selection.key } : {};
121
200
  }
@@ -159,7 +238,10 @@ export function isCursorQuotaError(error = "") {
159
238
  return classifyCursorApiKeyLimitError(error) !== undefined;
160
239
  }
161
240
 
162
- export function cursorApiKeyCooldownMinutes(env = {}) {
241
+ export function cursorApiKeyCooldownMinutes(env = {}, errorText = "") {
242
+ if (classifyCursorApiKeyLimitError(errorText) === "resource_exhausted") {
243
+ return Math.max(1, Number(env.AGENTFLOW_CURSOR_API_KEY_RESOURCE_EXHAUSTED_COOLDOWN_MINUTES || 3) || 3);
244
+ }
163
245
  return Math.max(1, Number(env.AGENTFLOW_CURSOR_API_KEY_COOLDOWN_MINUTES || env.CURSOR_API_KEY_COOLDOWN_MINUTES || 30) || 30);
164
246
  }
165
247
 
@@ -178,3 +260,26 @@ function selectionId(keyOrSelection) {
178
260
  function legacyKeyId(key) {
179
261
  return `legacy_${createHash("sha256").update(String(key || "")).digest("hex").slice(0, 16)}`;
180
262
  }
263
+
264
+ function buildLaneCooldowns(keyState, now) {
265
+ if (!keyState) return [];
266
+ const lanes = [];
267
+ for (const lane of ["auto", "fallback"]) {
268
+ const laneState = keyState[lane];
269
+ if (!laneState || (laneState.blockedUntil || 0) <= now) continue;
270
+ const fallbackModel = keyState.fallbackModel;
271
+ lanes.push({
272
+ lane,
273
+ modelId: lane === "auto" ? "auto" : fallbackModel?.id || "fallback",
274
+ modelName: lane === "auto" ? "Auto" : fallbackModel?.displayName || "降级模型",
275
+ ...(laneState.errorCategory ? { errorCategory: laneState.errorCategory } : {}),
276
+ blockedUntil: new Date(laneState.blockedUntil).toISOString(),
277
+ remainingSeconds: Math.ceil((laneState.blockedUntil - now) / 1000),
278
+ });
279
+ }
280
+ return lanes;
281
+ }
282
+
283
+ function sanitizeErrorPreview(errorText) {
284
+ return String(errorText || "").replace(/(?:sk|key)[-_][A-Za-z0-9_-]{8,}/gi, "[redacted]").trim().slice(0, 500);
285
+ }
@@ -0,0 +1,17 @@
1
+ const runFinishedListeners = new Set();
2
+
3
+ export function onRepositoryRunFinished(listener) {
4
+ if (typeof listener !== "function") return () => {};
5
+ runFinishedListeners.add(listener);
6
+ return () => runFinishedListeners.delete(listener);
7
+ }
8
+
9
+ export function emitRepositoryRunFinished(workspaceRoot, run, status) {
10
+ for (const listener of runFinishedListeners) {
11
+ try {
12
+ listener(workspaceRoot, run, status);
13
+ } catch {
14
+ // Derived repository updates must never break the authoritative run ledger.
15
+ }
16
+ }
17
+ }