oh-pi 0.1.59 → 0.1.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-pi",
3
- "version": "0.1.59",
3
+ "version": "0.1.60",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -159,7 +159,7 @@ interface WaveOptions {
159
159
  /**
160
160
  * 并发执行一批蚂蚁,自适应调节并发度
161
161
  */
162
- async function runAntWave(opts: WaveOptions): Promise<"ok"> {
162
+ async function runAntWave(opts: WaveOptions): Promise<"ok" | "budget"> {
163
163
  const { nest, cwd, caste, signal, callbacks, currentModel } = opts;
164
164
  const casteModel = opts.modelOverrides?.[caste] || currentModel;
165
165
  const config = { ...DEFAULT_ANT_CONFIGS[caste], model: casteModel };
@@ -168,7 +168,19 @@ async function runAntWave(opts: WaveOptions): Promise<"ok"> {
168
168
  let consecutiveRateLimits = 0; // 连续限流计数
169
169
  const retriedTasks = new Set<string>(); // 防止重复重试
170
170
 
171
- const runOne = async (): Promise<"done" | "empty" | "rate_limited"> => {
171
+ const runOne = async (): Promise<"done" | "empty" | "rate_limited" | "budget"> => {
172
+ // Budget 刹车:剩余预算不够一只蚂蚁的预估成本就不出发
173
+ const state = nest.getState();
174
+ if (state.maxCost != null && caste !== "drone") {
175
+ const spent = state.ants.reduce((s, a) => s + a.usage.cost, 0);
176
+ const remaining = state.maxCost - spent;
177
+ const doneAnts = state.ants.filter(a => a.status === "done" && a.usage.cost > 0);
178
+ const avgCost = doneAnts.length > 0
179
+ ? doneAnts.reduce((s, a) => s + a.usage.cost, 0) / doneAnts.length
180
+ : 0.05;
181
+ if (remaining < avgCost * 1.5) return "budget";
182
+ }
183
+
172
184
  const task = nest.nextPendingTask(caste);
173
185
  if (!task) return "empty";
174
186
  if (!nest.claimTask(task.id, "queen")) return "empty";
@@ -280,12 +292,16 @@ async function runAntWave(opts: WaveOptions): Promise<"ok"> {
280
292
  }
281
293
 
282
294
  const batch = Math.min(slotsAvailable, pending.length);
283
- const promises: Promise<"done" | "empty" | "rate_limited">[] = [];
295
+ const promises: Promise<"done" | "empty" | "rate_limited" | "budget">[] = [];
284
296
  for (let i = 0; i < batch; i++) {
285
297
  promises.push(runOne());
286
298
  }
287
299
  const results = await Promise.all(promises);
288
300
 
301
+ if (results.includes("budget")) {
302
+ return "budget";
303
+ }
304
+
289
305
  // 429 处理:降低并发 + 渐进退避(2s → 5s → 10s,上限 10s)
290
306
  if (results.includes("rate_limited")) {
291
307
  consecutiveRateLimits++;
@@ -431,9 +447,8 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
431
447
  emitSignal("working", `${workerTasks.length} tasks to do`);
432
448
  await runAntWave({ ...waveBase, caste: "worker" });
433
449
 
434
- // 处理工蚁产生的子任务(可能有多轮)
435
- let rounds = 0;
436
- while (rounds < 3) {
450
+ // 处理工蚁产生的子任务(budget 驱动,无硬限制)
451
+ while (true) {
437
452
  // 先跑 drone 子任务
438
453
  const pendingDrones = nest.getAllTasks().filter(t => t.caste === "drone" && t.status === "pending");
439
454
  if (pendingDrones.length > 0) await runAntWave({ ...waveBase, caste: "drone" });
@@ -442,9 +457,15 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
442
457
  t.caste === "worker" && (t.status === "pending" || t.status === "blocked")
443
458
  );
444
459
  if (remaining.length === 0) break;
445
- rounds++;
446
- callbacks.onPhase?.("working", `Round ${rounds + 1}: ${remaining.length} sub-tasks from workers...`);
447
- await runAntWave({ ...waveBase, caste: "worker" });
460
+ callbacks.onPhase?.("working", `${remaining.length} sub-tasks from workers...`);
461
+ const result = await runAntWave({ ...waveBase, caste: "worker" });
462
+ if (result === "budget") {
463
+ nest.updateState({ status: "budget_exceeded", finishedAt: Date.now() });
464
+ emitSignal("budget_exceeded", "Budget exhausted");
465
+ const budgetState = nest.getState();
466
+ callbacks.onComplete?.(budgetState);
467
+ return budgetState;
468
+ }
448
469
  }
449
470
 
450
471
  // ═══ Auto-check: run tsc before soldier review ═══