oh-pi 0.1.59 → 0.1.61

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.61",
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,14 @@ 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 刹车:预算用完就不出发(drone 免费,不检查)
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
+ if (spent >= state.maxCost) return "budget";
177
+ }
178
+
172
179
  const task = nest.nextPendingTask(caste);
173
180
  if (!task) return "empty";
174
181
  if (!nest.claimTask(task.id, "queen")) return "empty";
@@ -280,12 +287,16 @@ async function runAntWave(opts: WaveOptions): Promise<"ok"> {
280
287
  }
281
288
 
282
289
  const batch = Math.min(slotsAvailable, pending.length);
283
- const promises: Promise<"done" | "empty" | "rate_limited">[] = [];
290
+ const promises: Promise<"done" | "empty" | "rate_limited" | "budget">[] = [];
284
291
  for (let i = 0; i < batch; i++) {
285
292
  promises.push(runOne());
286
293
  }
287
294
  const results = await Promise.all(promises);
288
295
 
296
+ if (results.includes("budget")) {
297
+ return "budget";
298
+ }
299
+
289
300
  // 429 处理:降低并发 + 渐进退避(2s → 5s → 10s,上限 10s)
290
301
  if (results.includes("rate_limited")) {
291
302
  consecutiveRateLimits++;
@@ -431,9 +442,8 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
431
442
  emitSignal("working", `${workerTasks.length} tasks to do`);
432
443
  await runAntWave({ ...waveBase, caste: "worker" });
433
444
 
434
- // 处理工蚁产生的子任务(可能有多轮)
435
- let rounds = 0;
436
- while (rounds < 3) {
445
+ // 处理工蚁产生的子任务(budget 驱动,无硬限制)
446
+ while (true) {
437
447
  // 先跑 drone 子任务
438
448
  const pendingDrones = nest.getAllTasks().filter(t => t.caste === "drone" && t.status === "pending");
439
449
  if (pendingDrones.length > 0) await runAntWave({ ...waveBase, caste: "drone" });
@@ -442,9 +452,15 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
442
452
  t.caste === "worker" && (t.status === "pending" || t.status === "blocked")
443
453
  );
444
454
  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" });
455
+ callbacks.onPhase?.("working", `${remaining.length} sub-tasks from workers...`);
456
+ const result = await runAntWave({ ...waveBase, caste: "worker" });
457
+ if (result === "budget") {
458
+ nest.updateState({ status: "budget_exceeded", finishedAt: Date.now() });
459
+ emitSignal("budget_exceeded", "Budget exhausted");
460
+ const budgetState = nest.getState();
461
+ callbacks.onComplete?.(budgetState);
462
+ return budgetState;
463
+ }
448
464
  }
449
465
 
450
466
  // ═══ Auto-check: run tsc before soldier review ═══