oh-pi 0.1.37 → 0.1.39

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.37",
3
+ "version": "0.1.39",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -110,6 +110,9 @@ For simple single-file tasks, work directly without the colony.`,
110
110
  goal: Type.String({ description: "What the colony should accomplish" }),
111
111
  maxAnts: Type.Optional(Type.Number({ description: "Max concurrent ants (default: auto-adapt)", minimum: 1, maximum: 8 })),
112
112
  maxCost: Type.Optional(Type.Number({ description: "Max cost budget in USD (default: unlimited)", minimum: 0.01 })),
113
+ scoutModel: Type.Optional(Type.String({ description: "Model for scout ants (default: current session model)" })),
114
+ workerModel: Type.Optional(Type.String({ description: "Model for worker ants (default: current session model)" })),
115
+ soldierModel: Type.Optional(Type.String({ description: "Model for soldier ants (default: current session model)" })),
113
116
  }),
114
117
 
115
118
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
@@ -169,12 +172,18 @@ For simple single-file tasks, work directly without the colony.`,
169
172
  appendFileSync(gitignorePath, `${content.length && !content.endsWith("\n") ? "\n" : ""}.ant-colony/\n`);
170
173
  }
171
174
 
175
+ const modelOverrides: Record<string, string> = {};
176
+ if (params.scoutModel) modelOverrides.scout = params.scoutModel;
177
+ if (params.workerModel) modelOverrides.worker = params.workerModel;
178
+ if (params.soldierModel) modelOverrides.soldier = params.soldierModel;
179
+
172
180
  const state = await runColony({
173
181
  cwd: ctx.cwd,
174
182
  goal: params.goal,
175
183
  maxAnts: params.maxAnts,
176
184
  maxCost: params.maxCost,
177
185
  currentModel,
186
+ modelOverrides,
178
187
  signal: signal ?? undefined,
179
188
  callbacks,
180
189
  });
@@ -241,21 +250,81 @@ For simple single-file tasks, work directly without the colony.`,
241
250
  const details = result.details as ColonyDetails | undefined;
242
251
 
243
252
  // ─── 运行中 ───
244
- if (!details?.state) {
253
+ if (!details?.state || (details.state.status !== "done" && details.state.status !== "failed")) {
254
+ const state = details?.state;
245
255
  const log = details?.log ?? [];
246
256
  const container = new Container();
247
- container.addChild(new Text(
248
- theme.fg("warning", "🐜 ") + theme.fg("toolTitle", theme.bold("Colony ")) +
249
- theme.fg("accent", details?.phase || "initializing..."),
250
- 0, 0,
251
- ));
252
- const recent = log.slice(expanded ? -20 : -5);
253
- if (recent.length > 0) {
254
- container.addChild(new Text(recent.map(l => theme.fg("dim", ` ${l}`)).join("\n"), 0, 0));
257
+
258
+ if (state) {
259
+ const m = state.metrics;
260
+ const elapsed = formatDuration(Date.now() - state.createdAt);
261
+
262
+ // 标题行:● N ants launched (phase)
263
+ const activeAnts = state.ants.filter(a => a.status === "working");
264
+ const totalAnts = state.ants.length;
265
+ container.addChild(new Text(
266
+ theme.fg("warning", "● ") +
267
+ theme.fg("toolTitle", theme.bold(`${totalAnts} ant${totalAnts !== 1 ? "s" : ""} launched `)) +
268
+ theme.fg("muted", `(${state.status}) `) +
269
+ theme.fg("dim", `${elapsed} │ ${formatCost(m.totalCost)}`),
270
+ 0, 0,
271
+ ));
272
+
273
+ // 进度条
274
+ if (m.tasksTotal > 0) {
275
+ container.addChild(new Text(` ${progressBar(m.tasksDone, m.tasksTotal, 20, theme)}`, 0, 0));
276
+ }
277
+
278
+ // 蚂蚁树
279
+ const ants = expanded ? state.ants : state.ants.slice(-8);
280
+ for (let i = 0; i < ants.length; i++) {
281
+ const a = ants[i];
282
+ const isLast = i === ants.length - 1;
283
+ const branch = isLast ? "└─" : "├─";
284
+ const pipe = isLast ? " " : "│ ";
285
+
286
+ const statusDot = a.status === "working" ? theme.fg("warning", "◉")
287
+ : a.status === "done" ? theme.fg("success", "✓")
288
+ : theme.fg("error", "✗");
289
+
290
+ const task = state.tasks.find(t => t.id === a.taskId);
291
+ const taskTitle = task?.title?.slice(0, 55) || "...";
292
+ const dur = a.finishedAt ? formatDuration(a.finishedAt - a.startedAt) : formatDuration(Date.now() - a.startedAt);
293
+ const turns = a.usage.turns > 0 ? `${a.usage.turns}t` : "";
294
+ const model = a.model ? a.model.split("/").pop()! : "";
295
+
296
+ container.addChild(new Text(
297
+ theme.fg("muted", ` ${branch} `) + statusDot + " " +
298
+ theme.fg("accent", `@${a.id.slice(0, 20)} `) +
299
+ theme.fg("dim", `(${a.caste}) ${dur}${turns ? " │ " + turns : ""}`) +
300
+ (model ? " " + theme.fg("muted", model) : ""),
301
+ 0, 0,
302
+ ));
303
+ container.addChild(new Text(
304
+ theme.fg("muted", ` ${pipe}`) + theme.fg("dim", `⎿ ${taskTitle}`),
305
+ 0, 0,
306
+ ));
307
+ }
308
+ if (!expanded && state.ants.length > 8) {
309
+ container.addChild(new Text(theme.fg("muted", ` ⋯ +${state.ants.length - 8} more (expand to see all)`), 0, 0));
310
+ }
311
+ } else {
312
+ container.addChild(new Text(
313
+ theme.fg("warning", "● ") + theme.fg("toolTitle", theme.bold("Colony ")) +
314
+ theme.fg("accent", details?.phase || "initializing..."),
315
+ 0, 0,
316
+ ));
255
317
  }
256
- if (!expanded && log.length > 5) {
257
- container.addChild(new Text(theme.fg("muted", ` ⋯ ${log.length - 5} more`), 0, 0));
318
+
319
+ // 最近日志(仅展开时)
320
+ if (expanded && log.length > 0) {
321
+ container.addChild(new Spacer(1));
322
+ const recent = log.slice(-10);
323
+ for (const l of recent) {
324
+ container.addChild(new Text(theme.fg("dim", ` ${l}`), 0, 0));
325
+ }
258
326
  }
327
+
259
328
  return container;
260
329
  }
261
330
 
@@ -37,6 +37,7 @@ export interface QueenOptions {
37
37
  maxAnts?: number;
38
38
  maxCost?: number;
39
39
  currentModel: string;
40
+ modelOverrides?: ModelOverrides;
40
41
  signal?: AbortSignal;
41
42
  callbacks: QueenCallbacks;
42
43
  }
@@ -138,6 +139,7 @@ interface WaveOptions {
138
139
  cwd: string;
139
140
  caste: AntCaste;
140
141
  currentModel: string;
142
+ modelOverrides?: ModelOverrides;
141
143
  signal?: AbortSignal;
142
144
  callbacks: QueenCallbacks;
143
145
  }
@@ -147,7 +149,8 @@ interface WaveOptions {
147
149
  */
148
150
  async function runAntWave(opts: WaveOptions): Promise<"ok"> {
149
151
  const { nest, cwd, caste, signal, callbacks, currentModel } = opts;
150
- const config = { ...DEFAULT_ANT_CONFIGS[caste], model: currentModel };
152
+ const casteModel = opts.modelOverrides?.[caste] || currentModel;
153
+ const config = { ...DEFAULT_ANT_CONFIGS[caste], model: casteModel };
151
154
 
152
155
  let backoffMs = 0; // 429 退避时间
153
156
  let consecutiveRateLimits = 0; // 连续限流计数
@@ -160,7 +163,8 @@ async function runAntWave(opts: WaveOptions): Promise<"ok"> {
160
163
 
161
164
  const ant: Ant = {
162
165
  id: "", caste, status: "idle", taskId: task.id,
163
- pid: null, usage: { input: 0, output: 0, cost: 0, turns: 0 },
166
+ pid: null, model: casteModel,
167
+ usage: { input: 0, output: 0, cost: 0, turns: 0 },
164
168
  startedAt: Date.now(), finishedAt: null,
165
169
  };
166
170
  callbacks.onAntSpawn(ant, task);
@@ -311,6 +315,7 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
311
315
  const waveBase: Omit<WaveOptions, "caste"> = {
312
316
  nest, cwd: opts.cwd, signal, callbacks,
313
317
  currentModel: opts.currentModel,
318
+ modelOverrides: opts.modelOverrides,
314
319
  };
315
320
 
316
321
  const cleanup = () => {
@@ -216,6 +216,7 @@ export async function spawnAnt(
216
216
  status: "working",
217
217
  taskId: task.id,
218
218
  pid: null,
219
+ model: antConfig.model,
219
220
  usage: { input: 0, output: 0, cost: 0, turns: 0 },
220
221
  startedAt: Date.now(),
221
222
  finishedAt: null,
@@ -73,6 +73,7 @@ export interface Ant {
73
73
  status: AntStatus;
74
74
  taskId: string | null;
75
75
  pid: number | null;
76
+ model: string;
76
77
  usage: { input: number; output: number; cost: number; turns: number };
77
78
  startedAt: number;
78
79
  finishedAt: number | null;