oh-pi 0.1.38 → 0.1.40

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.38",
3
+ "version": "0.1.40",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -79,7 +79,19 @@ function phasePipeline(status: string, theme: any): string {
79
79
  export default function antColonyExtension(pi: ExtensionAPI) {
80
80
 
81
81
  // ═══ Auto-trigger: 注入蚁群意识,LLM 自动判断何时启动 ═══
82
- pi.on("before_agent_start", async () => {
82
+ pi.on("before_agent_start", async (ctx) => {
83
+ // 获取可用模型列表
84
+ let modelList = "";
85
+ try {
86
+ const { execSync } = await import("node:child_process");
87
+ const output = execSync("pi --list-models 2>/dev/null", { encoding: "utf-8", timeout: 5000 });
88
+ const models = output.trim().split("\n").slice(1) // skip header
89
+ .map(l => l.trim().split(/\s+/))
90
+ .filter(p => p.length >= 2)
91
+ .map(p => `${p[0]}/${p[1]}`);
92
+ if (models.length > 0) modelList = models.join(", ");
93
+ } catch { /* ignore */ }
94
+
83
95
  return {
84
96
  message: {
85
97
  customType: "ant-colony-context",
@@ -90,7 +102,17 @@ You have the ant_colony tool. Use it automatically when:
90
102
  - Task is complex enough that serial execution is inefficient
91
103
 
92
104
  Do NOT ask for confirmation. Call ant_colony directly with a clear goal.
93
- For simple single-file tasks, work directly without the colony.`,
105
+ For simple single-file tasks, work directly without the colony.
106
+ ${modelList ? `
107
+ [COLONY MODEL SELECTION]
108
+ Available models: ${modelList}
109
+
110
+ Strategy for choosing per-caste models:
111
+ - scoutModel: Use a fast/cheap model (e.g. haiku, flash, gpt-4o-mini). Scouts only read, no edits.
112
+ - workerModel: Use a capable model (e.g. sonnet, opus, gpt-4o). Workers make code changes.
113
+ - soldierModel: Use same as worker or slightly cheaper. Soldiers review but don't edit.
114
+ - If unsure, omit all three — defaults to current session model.
115
+ - Prefer latest model versions for best quality.` : ""}`,
94
116
  display: false,
95
117
  },
96
118
  };
@@ -110,6 +132,9 @@ For simple single-file tasks, work directly without the colony.`,
110
132
  goal: Type.String({ description: "What the colony should accomplish" }),
111
133
  maxAnts: Type.Optional(Type.Number({ description: "Max concurrent ants (default: auto-adapt)", minimum: 1, maximum: 8 })),
112
134
  maxCost: Type.Optional(Type.Number({ description: "Max cost budget in USD (default: unlimited)", minimum: 0.01 })),
135
+ scoutModel: Type.Optional(Type.String({ description: "Model for scout ants (default: current session model)" })),
136
+ workerModel: Type.Optional(Type.String({ description: "Model for worker ants (default: current session model)" })),
137
+ soldierModel: Type.Optional(Type.String({ description: "Model for soldier ants (default: current session model)" })),
113
138
  }),
114
139
 
115
140
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
@@ -169,12 +194,18 @@ For simple single-file tasks, work directly without the colony.`,
169
194
  appendFileSync(gitignorePath, `${content.length && !content.endsWith("\n") ? "\n" : ""}.ant-colony/\n`);
170
195
  }
171
196
 
197
+ const modelOverrides: Record<string, string> = {};
198
+ if (params.scoutModel) modelOverrides.scout = params.scoutModel;
199
+ if (params.workerModel) modelOverrides.worker = params.workerModel;
200
+ if (params.soldierModel) modelOverrides.soldier = params.soldierModel;
201
+
172
202
  const state = await runColony({
173
203
  cwd: ctx.cwd,
174
204
  goal: params.goal,
175
205
  maxAnts: params.maxAnts,
176
206
  maxCost: params.maxCost,
177
207
  currentModel,
208
+ modelOverrides,
178
209
  signal: signal ?? undefined,
179
210
  callbacks,
180
211
  });
@@ -282,11 +313,13 @@ For simple single-file tasks, work directly without the colony.`,
282
313
  const taskTitle = task?.title?.slice(0, 55) || "...";
283
314
  const dur = a.finishedAt ? formatDuration(a.finishedAt - a.startedAt) : formatDuration(Date.now() - a.startedAt);
284
315
  const turns = a.usage.turns > 0 ? `${a.usage.turns}t` : "";
316
+ const model = a.model ? a.model.split("/").pop()! : "";
285
317
 
286
318
  container.addChild(new Text(
287
319
  theme.fg("muted", ` ${branch} `) + statusDot + " " +
288
320
  theme.fg("accent", `@${a.id.slice(0, 20)} `) +
289
- theme.fg("dim", `(${a.caste}) ${dur}${turns ? " │ " + turns : ""}`),
321
+ theme.fg("dim", `(${a.caste}) ${dur}${turns ? " │ " + turns : ""}`) +
322
+ (model ? " " + theme.fg("muted", model) : ""),
290
323
  0, 0,
291
324
  ));
292
325
  container.addChild(new Text(
@@ -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;