oh-pi 0.1.54 → 0.1.55
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
|
@@ -43,7 +43,7 @@ function statusIcon(status: string): string {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function casteIcon(caste: string): string {
|
|
46
|
-
return caste === "scout" ? "🔍" : caste === "soldier" ? "🛡️" : "⚒️";
|
|
46
|
+
return caste === "scout" ? "🔍" : caste === "soldier" ? "🛡️" : caste === "drone" ? "⚙️" : "⚒️";
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// ═══ Background colony state ═══
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
} from "./types.js";
|
|
21
21
|
import { DEFAULT_ANT_CONFIGS } from "./types.js";
|
|
22
22
|
import { Nest } from "./nest.js";
|
|
23
|
-
import { spawnAnt, makeTaskId } from "./spawner.js";
|
|
23
|
+
import { spawnAnt, runDrone, makeTaskId } from "./spawner.js";
|
|
24
24
|
import { adapt, sampleSystem, defaultConcurrency } from "./concurrency.js";
|
|
25
25
|
import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
26
26
|
|
|
@@ -177,7 +177,9 @@ async function runAntWave(opts: WaveOptions): Promise<"ok"> {
|
|
|
177
177
|
callbacks.onAntSpawn(ant, task);
|
|
178
178
|
|
|
179
179
|
try {
|
|
180
|
-
const result =
|
|
180
|
+
const result = caste === "drone"
|
|
181
|
+
? await runDrone(cwd, nest, task)
|
|
182
|
+
: await spawnAnt(cwd, nest, task, config, signal, callbacks.onAntStream, opts.authStorage, opts.modelRegistry);
|
|
181
183
|
callbacks.onAntDone(result.ant, task, result.output);
|
|
182
184
|
|
|
183
185
|
if (result.rateLimited) {
|
|
@@ -344,7 +346,7 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
344
346
|
callbacks.onPhase("scouting", "Dispatching scout ant to explore codebase...");
|
|
345
347
|
await runAntWave({ ...waveBase, caste: "scout" });
|
|
346
348
|
|
|
347
|
-
let workerTasks = nest.getAllTasks().filter(t => t.caste === "worker" && t.status === "pending");
|
|
349
|
+
let workerTasks = nest.getAllTasks().filter(t => (t.caste === "worker" || t.caste === "drone") && t.status === "pending");
|
|
348
350
|
|
|
349
351
|
// 只在完全没有 worker 任务时才重试一次
|
|
350
352
|
if (workerTasks.length === 0) {
|
|
@@ -372,7 +374,7 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
372
374
|
nest.writeTask(relayTask);
|
|
373
375
|
callbacks.onPhase("scouting", "Scout relay: generating worker tasks...");
|
|
374
376
|
await runAntWave({ ...waveBase, caste: "scout" });
|
|
375
|
-
workerTasks = nest.getAllTasks().filter(t => t.caste === "worker" && t.status === "pending");
|
|
377
|
+
workerTasks = nest.getAllTasks().filter(t => (t.caste === "worker" || t.caste === "drone") && t.status === "pending");
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
if (workerTasks.length === 0) {
|
|
@@ -384,12 +386,24 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
384
386
|
|
|
385
387
|
// ═══ Phase 2: 工作 ═══
|
|
386
388
|
nest.updateState({ status: "working" });
|
|
389
|
+
|
|
390
|
+
// 先执行 drone 任务(零 LLM 成本)
|
|
391
|
+
const droneTasks = nest.getAllTasks().filter(t => t.caste === "drone" && t.status === "pending");
|
|
392
|
+
if (droneTasks.length > 0) {
|
|
393
|
+
callbacks.onPhase("working", `${droneTasks.length} drone tasks. Executing rules...`);
|
|
394
|
+
await runAntWave({ ...waveBase, caste: "drone" });
|
|
395
|
+
}
|
|
396
|
+
|
|
387
397
|
callbacks.onPhase("working", `${workerTasks.length} tasks discovered. Dispatching worker ants...`);
|
|
388
398
|
await runAntWave({ ...waveBase, caste: "worker" });
|
|
389
399
|
|
|
390
400
|
// 处理工蚁产生的子任务(可能有多轮)
|
|
391
401
|
let rounds = 0;
|
|
392
402
|
while (rounds < 3) {
|
|
403
|
+
// 先跑 drone 子任务
|
|
404
|
+
const pendingDrones = nest.getAllTasks().filter(t => t.caste === "drone" && t.status === "pending");
|
|
405
|
+
if (pendingDrones.length > 0) await runAntWave({ ...waveBase, caste: "drone" });
|
|
406
|
+
|
|
393
407
|
const remaining = nest.getAllTasks().filter(t =>
|
|
394
408
|
t.caste === "worker" && (t.status === "pending" || t.status === "blocked")
|
|
395
409
|
);
|
|
@@ -83,6 +83,8 @@ For each task the colony should do next:
|
|
|
83
83
|
- priority: <1-5, 1=highest>
|
|
84
84
|
- context: <relevant code snippets that the worker will need, with file:line references>
|
|
85
85
|
|
|
86
|
+
Use caste "drone" instead of "worker" for simple tasks that can be done with a single bash command (file copy, find-replace, formatting, running tests). Drone description should be the exact bash command to execute.
|
|
87
|
+
|
|
86
88
|
## Warnings
|
|
87
89
|
Any risks, blockers, or conflicts detected.`,
|
|
88
90
|
|
|
@@ -250,6 +252,49 @@ function makeMinimalResourceLoader(systemPrompt: string): ResourceLoader {
|
|
|
250
252
|
};
|
|
251
253
|
}
|
|
252
254
|
|
|
255
|
+
/**
|
|
256
|
+
* 运行 Drone — 纯规则执行,零 LLM 成本
|
|
257
|
+
* 任务描述即为要执行的 bash 命令
|
|
258
|
+
*/
|
|
259
|
+
export async function runDrone(
|
|
260
|
+
cwd: string,
|
|
261
|
+
nest: Nest,
|
|
262
|
+
task: Task,
|
|
263
|
+
): Promise<AntResult> {
|
|
264
|
+
const antId = makeAntId("drone");
|
|
265
|
+
const ant: Ant = {
|
|
266
|
+
id: antId, caste: "drone", status: "working", taskId: task.id,
|
|
267
|
+
pid: null, model: "none",
|
|
268
|
+
usage: { input: 0, output: 0, cost: 0, turns: 1 },
|
|
269
|
+
startedAt: Date.now(), finishedAt: null,
|
|
270
|
+
};
|
|
271
|
+
nest.updateAnt(ant);
|
|
272
|
+
nest.updateTaskStatus(task.id, "active");
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const { execSync } = await import("node:child_process");
|
|
276
|
+
const cmd = task.description;
|
|
277
|
+
const output = execSync(cmd, { cwd, encoding: "utf-8", timeout: 30000, stdio: "pipe" }).trim();
|
|
278
|
+
|
|
279
|
+
ant.status = "done";
|
|
280
|
+
ant.finishedAt = Date.now();
|
|
281
|
+
nest.updateAnt(ant);
|
|
282
|
+
nest.updateTaskStatus(task.id, "done", `## Completed\n${output || "(no output)"}`);
|
|
283
|
+
nest.dropPheromone({
|
|
284
|
+
id: makePheromoneId(), type: "completion", antId, antCaste: "drone",
|
|
285
|
+
taskId: task.id, content: `Drone executed: ${cmd.slice(0, 100)}`, files: task.files, strength: 1, createdAt: Date.now(),
|
|
286
|
+
});
|
|
287
|
+
return { ant, output, newTasks: [], pheromones: [], rateLimited: false };
|
|
288
|
+
} catch (e: any) {
|
|
289
|
+
const errStr = e.stderr?.toString() || String(e);
|
|
290
|
+
ant.status = "failed";
|
|
291
|
+
ant.finishedAt = Date.now();
|
|
292
|
+
nest.updateAnt(ant);
|
|
293
|
+
nest.updateTaskStatus(task.id, "failed", undefined, errStr.slice(0, 500));
|
|
294
|
+
return { ant, output: errStr, newTasks: [], pheromones: [], rateLimited: false };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
253
298
|
/**
|
|
254
299
|
* 孵化并运行一只蚂蚁 — SDK 内嵌版
|
|
255
300
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// ═══ 蚂蚁角色 ═══
|
|
6
|
-
export type AntCaste = "scout" | "worker" | "soldier";
|
|
6
|
+
export type AntCaste = "scout" | "worker" | "soldier" | "drone";
|
|
7
7
|
|
|
8
8
|
export interface AntConfig {
|
|
9
9
|
caste: AntCaste;
|
|
@@ -17,6 +17,7 @@ export const DEFAULT_ANT_CONFIGS: Record<AntCaste, Omit<AntConfig, "systemPrompt
|
|
|
17
17
|
scout: { caste: "scout", model: "", tools: ["read", "bash", "grep", "find", "ls"], maxTurns: 8 },
|
|
18
18
|
worker: { caste: "worker", model: "", tools: ["read", "bash", "edit", "write", "grep", "find", "ls"], maxTurns: 15 },
|
|
19
19
|
soldier: { caste: "soldier", model: "", tools: ["read", "bash", "grep", "find", "ls"], maxTurns: 8 },
|
|
20
|
+
drone: { caste: "drone", model: "", tools: ["bash"], maxTurns: 1 },
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
/** Per-caste model overrides from user config */
|