oh-pi 0.1.50 → 0.1.51

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.50",
3
+ "version": "0.1.51",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@ You command an autonomous ant colony. Complex tasks are delegated to the swarm,
16
16
 
17
17
  ## Workflow
18
18
  1. Assess task scope
19
- 2. If colony-worthy → use `ant_colony` tool with clear goal
19
+ 2. If colony-worthy → use `/colony <goal>` command with clear goal
20
20
  3. If simple → do it directly
21
21
  4. Review colony output, fix gaps manually if needed
22
22
 
@@ -334,152 +334,9 @@ export default function antColonyExtension(pi: ExtensionAPI) {
334
334
  });
335
335
  }
336
336
 
337
- // ═══ Auto-trigger: 注入蚁群意识 ═══
338
- pi.on("before_agent_start", async () => {
339
- let modelList = "";
340
- try {
341
- const { execSync } = await import("node:child_process");
342
- const output = execSync("pi --list-models 2>/dev/null", { encoding: "utf-8", timeout: 5000 });
343
- const models = output.trim().split("\n").slice(1)
344
- .map(l => l.trim().split(/\s+/))
345
- .filter(p => p.length >= 2)
346
- .map(p => `${p[0]}/${p[1]}`);
347
- if (models.length > 0) modelList = models.join(", ");
348
- } catch { /* ignore */ }
349
-
350
- const colonyStatus = activeColony
351
- ? `\n[COLONY_SIGNAL:RUNNING] A colony is currently running in the background for goal: "${activeColony.goal.slice(0, 100)}". Do NOT launch another colony. Do NOT check progress or assume failure. You will receive [COLONY_SIGNAL:COMPLETE] automatically when it finishes. If the user asks about colony status, tell them it's still running. Use /colony-stop to cancel.`
352
- : "";
353
-
354
- return {
355
- message: {
356
- customType: "ant-colony-context",
357
- content: `[ANT COLONY AVAILABLE]
358
- You have the ant_colony tool. Use it automatically when:
359
- - Task involves ≥3 files
360
- - Task needs parallel workstreams (refactor, migration, feature across modules)
361
- - Task is complex enough that serial execution is inefficient
362
-
363
- Do NOT ask for confirmation. Call ant_colony directly with a clear goal.
364
- For simple single-file tasks, work directly without the colony.
365
-
366
- [COLONY SIGNAL PROTOCOL]
367
- The colony communicates via signals. You MUST obey these:
368
- - [COLONY_SIGNAL:LAUNCHED] — Colony started. Do NOT poll, check, or assume failure. Wait for completion signal.
369
- - [COLONY_SIGNAL:RUNNING] — Colony is active. Do NOT launch another or check progress.
370
- - [COLONY_SIGNAL:COMPLETE] — Colony finished. Review the report and summarize results to the user.
371
- - [COLONY_SIGNAL:FAILED] — Colony crashed. Report the error to the user.
372
-
373
- After launching a colony, your ONLY correct behavior is:
374
- 1. Tell the user the colony is running
375
- 2. Continue chatting about OTHER topics if the user asks
376
- 3. Wait for [COLONY_SIGNAL:COMPLETE] or [COLONY_SIGNAL:FAILED] — do NOT guess the outcome
377
- ${modelList ? `
378
- [COLONY MODEL SELECTION]
379
- Available models: ${modelList}
380
-
381
- Strategy for choosing per-caste models:
382
- - scoutModel: Use a fast/cheap model (e.g. haiku, flash, gpt-4o-mini). Scouts only read, no edits.
383
- - workerModel: Use a capable model (e.g. sonnet, opus, gpt-4o). Workers make code changes.
384
- - soldierModel: Use same as worker or slightly cheaper. Soldiers review but don't edit.
385
- - If unsure, omit all three — defaults to current session model.
386
- - Prefer latest model versions for best quality.` : ""}${colonyStatus}`,
387
- display: false,
388
- },
389
- };
390
- });
391
-
392
- // ═══ Tool: ant_colony (non-blocking) ═══
393
- pi.registerTool({
394
- name: "ant_colony",
395
- label: "Ant Colony",
396
- description: [
397
- "Launch an autonomous ant colony in the BACKGROUND to accomplish a complex goal.",
398
- "The colony runs asynchronously — you can continue chatting while it works.",
399
- "Results are automatically injected when the colony finishes.",
400
- "Scouts explore the codebase, workers execute tasks in parallel, soldiers review quality.",
401
- "Use for multi-file changes, large refactors, or complex features.",
402
- ].join(" "),
403
- parameters: Type.Object({
404
- goal: Type.String({ description: "What the colony should accomplish" }),
405
- maxAnts: Type.Optional(Type.Number({ description: "Max concurrent ants (default: auto-adapt)", minimum: 1, maximum: 8 })),
406
- maxCost: Type.Optional(Type.Number({ description: "Max cost budget in USD (default: unlimited)", minimum: 0.01 })),
407
- scoutModel: Type.Optional(Type.String({ description: "Model for scout ants (default: current session model)" })),
408
- workerModel: Type.Optional(Type.String({ description: "Model for worker ants (default: current session model)" })),
409
- soldierModel: Type.Optional(Type.String({ description: "Model for soldier ants (default: current session model)" })),
410
- }),
411
-
412
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
413
- if (activeColony) {
414
- return {
415
- content: [{ type: "text", text: "A colony is already running in the background. Use /colony-stop to cancel it first." }],
416
- isError: true,
417
- };
418
- }
419
-
420
- const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null;
421
- if (!currentModel) {
422
- return {
423
- content: [{ type: "text", text: "Colony failed: no model available in current session" }],
424
- isError: true,
425
- };
426
- }
427
-
428
- const modelOverrides: Record<string, string> = {};
429
- if (params.scoutModel) modelOverrides.scout = params.scoutModel;
430
- if (params.workerModel) modelOverrides.worker = params.workerModel;
431
- if (params.soldierModel) modelOverrides.soldier = params.soldierModel;
432
-
433
- const colonyParams = {
434
- goal: params.goal,
435
- maxAnts: params.maxAnts,
436
- maxCost: params.maxCost,
437
- currentModel,
438
- modelOverrides,
439
- cwd: ctx.cwd,
440
- modelRegistry: ctx.modelRegistry ?? undefined,
441
- };
442
-
443
- // 非交互模式(print mode):同步等待蚁群完成
444
- if (!ctx.hasUI) {
445
- return await runSyncColony(colonyParams, _signal);
446
- }
447
-
448
- // 交互模式:后台运行
449
- launchBackgroundColony(colonyParams);
450
337
 
451
- return {
452
- content: [{ type: "text", text: `[COLONY_SIGNAL:LAUNCHED]\n🐜 Colony launched in background.\nGoal: ${params.goal}\n\n⚠️ IMPORTANT: The colony is now running autonomously. Do NOT check progress, do NOT ask about status, do NOT assume failure. You will receive a [COLONY_SIGNAL:COMPLETE] message automatically when it finishes. Continue chatting about other topics or wait silently.` }],
453
- };
454
- },
455
338
 
456
- renderCall(args, theme) {
457
- const goal = args.goal?.length > 70 ? args.goal.slice(0, 67) + "..." : args.goal;
458
- let text = theme.fg("toolTitle", theme.bold("🐜 ant_colony"));
459
- if (args.maxAnts) text += theme.fg("muted", ` ×${args.maxAnts}`);
460
- if (args.maxCost) text += theme.fg("warning", ` $${args.maxCost}`);
461
- text += "\n" + theme.fg("dim", ` ${goal || "..."}`);
462
- return new Text(text, 0, 0);
463
- },
464
339
 
465
- renderResult(result, { expanded }, theme) {
466
- // 后台模式:tool result 只是启动确认
467
- const text = result.content?.find((c: any) => c.type === "text")?.text || "";
468
- if (result.isError) {
469
- return new Text(theme.fg("error", text), 0, 0);
470
- }
471
- const container = new Container();
472
- container.addChild(new Text(
473
- theme.fg("success", "✓ ") + theme.fg("toolTitle", theme.bold("Colony launched in background")),
474
- 0, 0,
475
- ));
476
- if (activeColony) {
477
- container.addChild(new Text(theme.fg("dim", ` Goal: ${activeColony.goal.slice(0, 70)}`), 0, 0));
478
- container.addChild(new Text(theme.fg("muted", ` Ctrl+Shift+A for details │ /colony-stop to cancel`), 0, 0));
479
- }
480
- return container;
481
- },
482
- });
483
340
 
484
341
  // ═══ Custom message renderer for colony reports ═══
485
342
  pi.registerMessageRenderer("ant-colony-report", (message, theme) => {
@@ -610,6 +467,40 @@ Strategy for choosing per-caste models:
610
467
  },
611
468
  });
612
469
 
470
+ // ═══ Command: /colony ═══
471
+ pi.registerCommand("colony", {
472
+ description: "Launch an ant colony with a goal",
473
+ parameters: Type.Object({
474
+ goal: Type.String({ description: "What the colony should accomplish" }),
475
+ }),
476
+ async handler(args, ctx) {
477
+ const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null;
478
+ if (!currentModel) {
479
+ ctx.ui.notify("No model available in current session", "error");
480
+ return;
481
+ }
482
+
483
+ const colonyParams = {
484
+ goal: args.goal,
485
+ currentModel,
486
+ modelOverrides: {},
487
+ cwd: ctx.cwd,
488
+ modelRegistry: ctx.modelRegistry ?? undefined,
489
+ };
490
+
491
+ if (!ctx.hasUI) {
492
+ const result = await runSyncColony(colonyParams);
493
+ if (result.isError) {
494
+ ctx.ui.notify("Colony failed", "error");
495
+ }
496
+ return;
497
+ }
498
+
499
+ launchBackgroundColony(colonyParams);
500
+ ctx.ui.notify(`🐜 Colony launched: ${args.goal.slice(0, 50)}`, "success");
501
+ },
502
+ });
503
+
613
504
  // ═══ Command: /colony-stop ═══
614
505
  pi.registerCommand("colony-stop", {
615
506
  description: "Stop the running background colony",