oh-pi 0.1.51 → 0.1.53
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/dist/bin/oh-pi.js +4 -0
- package/dist/tui/agents-select.d.ts +8 -0
- package/dist/tui/agents-select.js +8 -0
- package/dist/tui/confirm-apply.d.ts +5 -0
- package/dist/tui/confirm-apply.js +11 -0
- package/dist/tui/extension-select.d.ts +5 -0
- package/dist/tui/extension-select.js +5 -0
- package/dist/tui/keybinding-select.d.ts +7 -0
- package/dist/tui/keybinding-select.js +7 -0
- package/dist/tui/mode-select.d.ts +5 -0
- package/dist/tui/mode-select.js +5 -0
- package/dist/tui/preset-select.d.ts +5 -0
- package/dist/tui/preset-select.js +9 -0
- package/dist/tui/provider-setup.d.ts +5 -0
- package/dist/tui/provider-setup.js +30 -1
- package/dist/tui/theme-select.d.ts +5 -0
- package/dist/tui/theme-select.js +5 -0
- package/dist/tui/welcome.d.ts +4 -0
- package/dist/tui/welcome.js +9 -0
- package/dist/utils/resources.d.ts +1 -0
- package/dist/utils/resources.js +1 -0
- package/package.json +1 -1
- package/pi-package/agents/colony-operator.md +1 -1
- package/pi-package/extensions/ant-colony/README.md +5 -6
- package/pi-package/extensions/ant-colony/concurrency.ts +6 -1
- package/pi-package/extensions/ant-colony/index.ts +140 -14
- package/pi-package/extensions/ant-colony/nest.ts +2 -2
- package/pi-package/extensions/ant-colony/queen.ts +5 -3
- package/pi-package/extensions/ant-colony/spawner.ts +3 -0
- package/pi-package/skills/ant-colony/SKILL.md +0 -59
package/dist/bin/oh-pi.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @module oh-pi CLI 入口脚本
|
|
4
|
+
* 处理 Windows 终端 UTF-8 编码,然后启动主流程。
|
|
5
|
+
*/
|
|
2
6
|
import { execSync } from "node:child_process";
|
|
3
7
|
// Windows terminals default to non-UTF-8 codepage (e.g. GBK/CP936),
|
|
4
8
|
// causing garbled emoji and Unicode output. Force UTF-8 before any output.
|
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presents an interactive prompt for the user to select an agent template
|
|
3
|
+
* (e.g. general developer, fullstack, security, data/AI, colony operator).
|
|
4
|
+
*
|
|
5
|
+
* Exits the process if the user cancels the selection.
|
|
6
|
+
*
|
|
7
|
+
* @returns The selected agent template identifier string.
|
|
8
|
+
*/
|
|
1
9
|
export declare function selectAgents(): Promise<string>;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { t } from "../i18n.js";
|
|
3
|
+
/**
|
|
4
|
+
* Presents an interactive prompt for the user to select an agent template
|
|
5
|
+
* (e.g. general developer, fullstack, security, data/AI, colony operator).
|
|
6
|
+
*
|
|
7
|
+
* Exits the process if the user cancels the selection.
|
|
8
|
+
*
|
|
9
|
+
* @returns The selected agent template identifier string.
|
|
10
|
+
*/
|
|
3
11
|
export async function selectAgents() {
|
|
4
12
|
const agent = await p.select({
|
|
5
13
|
message: t("agent.select"),
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { OhPConfig } from "../types.js";
|
|
2
2
|
import type { EnvInfo } from "../utils/detect.js";
|
|
3
|
+
/**
|
|
4
|
+
* 展示配置摘要,处理已有配置的备份/覆盖,安装 pi(如需),并应用最终配置。
|
|
5
|
+
* @param config - 用户选择的配置对象
|
|
6
|
+
* @param env - 当前环境信息
|
|
7
|
+
*/
|
|
3
8
|
export declare function confirmApply(config: OhPConfig, env: EnvInfo): Promise<void>;
|
|
@@ -2,9 +2,20 @@ import * as p from "@clack/prompts";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { t } from "../i18n.js";
|
|
4
4
|
import { applyConfig, installPi, backupConfig } from "../utils/install.js";
|
|
5
|
+
/**
|
|
6
|
+
* 统计已有配置中指定目录下的文件数量。
|
|
7
|
+
* @param env - 环境信息
|
|
8
|
+
* @param dir - 目录名称前缀
|
|
9
|
+
* @returns 匹配的文件数
|
|
10
|
+
*/
|
|
5
11
|
function countExisting(env, dir) {
|
|
6
12
|
return env.existingFiles.filter(f => f.startsWith(dir + "/")).length;
|
|
7
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* 展示配置摘要,处理已有配置的备份/覆盖,安装 pi(如需),并应用最终配置。
|
|
16
|
+
* @param config - 用户选择的配置对象
|
|
17
|
+
* @param env - 当前环境信息
|
|
18
|
+
*/
|
|
8
19
|
export async function confirmApply(config, env) {
|
|
9
20
|
// ═══ Summary ═══
|
|
10
21
|
const summary = [
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts the user to select enabled extensions from the available list via a multi-select TUI prompt.
|
|
3
|
+
* Exits the process if the user cancels the selection.
|
|
4
|
+
* @returns A promise that resolves to an array of selected extension names.
|
|
5
|
+
*/
|
|
1
6
|
export declare function selectExtensions(): Promise<string[]>;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { t } from "../i18n.js";
|
|
3
3
|
import { EXTENSIONS } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Prompts the user to select enabled extensions from the available list via a multi-select TUI prompt.
|
|
6
|
+
* Exits the process if the user cancels the selection.
|
|
7
|
+
* @returns A promise that resolves to an array of selected extension names.
|
|
8
|
+
*/
|
|
4
9
|
export async function selectExtensions() {
|
|
5
10
|
const exts = await p.multiselect({
|
|
6
11
|
message: t("ext.select"),
|
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts the user to select a keybinding scheme (default, Vim, or Emacs).
|
|
3
|
+
*
|
|
4
|
+
* Displays an interactive select prompt and exits the process if the user cancels.
|
|
5
|
+
*
|
|
6
|
+
* @returns The name of the selected keybinding scheme.
|
|
7
|
+
*/
|
|
1
8
|
export declare function selectKeybindings(): Promise<string>;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { t } from "../i18n.js";
|
|
3
|
+
/**
|
|
4
|
+
* Prompts the user to select a keybinding scheme (default, Vim, or Emacs).
|
|
5
|
+
*
|
|
6
|
+
* Displays an interactive select prompt and exits the process if the user cancels.
|
|
7
|
+
*
|
|
8
|
+
* @returns The name of the selected keybinding scheme.
|
|
9
|
+
*/
|
|
3
10
|
export async function selectKeybindings() {
|
|
4
11
|
const kb = await p.select({
|
|
5
12
|
message: t("kb.select"),
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { EnvInfo } from "../utils/detect.js";
|
|
2
2
|
export type Mode = "quick" | "custom" | "preset";
|
|
3
|
+
/**
|
|
4
|
+
* Prompt the user to select a configuration mode: quick, preset, or custom.
|
|
5
|
+
* @param {EnvInfo} env - Detected environment information
|
|
6
|
+
* @returns {Promise<Mode>} The mode selected by the user
|
|
7
|
+
*/
|
|
3
8
|
export declare function selectMode(env: EnvInfo): Promise<Mode>;
|
package/dist/tui/mode-select.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { t } from "../i18n.js";
|
|
3
|
+
/**
|
|
4
|
+
* Prompt the user to select a configuration mode: quick, preset, or custom.
|
|
5
|
+
* @param {EnvInfo} env - Detected environment information
|
|
6
|
+
* @returns {Promise<Mode>} The mode selected by the user
|
|
7
|
+
*/
|
|
3
8
|
export async function selectMode(env) {
|
|
4
9
|
const mode = await p.select({
|
|
5
10
|
message: t("mode.select"),
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { OhPConfig } from "../types.js";
|
|
2
2
|
interface Preset extends Omit<OhPConfig, "providers"> {
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* Prompts the user to select a configuration preset via an interactive TUI menu.
|
|
6
|
+
* Exits the process if the user cancels the selection.
|
|
7
|
+
* @returns The {@link Preset} configuration object for the chosen preset.
|
|
8
|
+
*/
|
|
4
9
|
export declare function selectPreset(): Promise<Preset>;
|
|
5
10
|
export {};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { t } from "../i18n.js";
|
|
3
|
+
/**
|
|
4
|
+
* Registry of built-in configuration presets (Full Power / Clean / Colony).
|
|
5
|
+
* Each entry maps a preset key to its i18n label/hint keys and a full {@link Preset} config object.
|
|
6
|
+
*/
|
|
3
7
|
const PRESETS = {
|
|
4
8
|
full: {
|
|
5
9
|
labelKey: "preset.full", hintKey: "preset.fullHint",
|
|
@@ -27,6 +31,11 @@ const PRESETS = {
|
|
|
27
31
|
},
|
|
28
32
|
},
|
|
29
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Prompts the user to select a configuration preset via an interactive TUI menu.
|
|
36
|
+
* Exits the process if the user cancels the selection.
|
|
37
|
+
* @returns The {@link Preset} configuration object for the chosen preset.
|
|
38
|
+
*/
|
|
30
39
|
export async function selectPreset() {
|
|
31
40
|
const key = await p.select({
|
|
32
41
|
message: t("preset.select"),
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type ProviderConfig } from "../types.js";
|
|
2
2
|
import type { EnvInfo } from "../utils/detect.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interactively configure API providers, detecting existing keys, allowing multi-select, and supporting custom endpoints.
|
|
5
|
+
* @param env - Current environment info with detected providers
|
|
6
|
+
* @returns Configured provider list
|
|
7
|
+
*/
|
|
3
8
|
export declare function setupProviders(env?: EnvInfo): Promise<ProviderConfig[]>;
|
|
@@ -12,7 +12,13 @@ const PROVIDER_API_URLS = {
|
|
|
12
12
|
xai: "https://api.x.ai",
|
|
13
13
|
mistral: "https://api.mistral.ai",
|
|
14
14
|
};
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* 动态获取模型列表,依次尝试 Anthropic、Google、OpenAI 兼容 API 风格。
|
|
17
|
+
* @param provider - 提供商名称
|
|
18
|
+
* @param baseUrl - API 基础地址
|
|
19
|
+
* @param apiKey - API 密钥或环境变量名
|
|
20
|
+
* @returns 发现的模型列表及检测到的 API 类型
|
|
21
|
+
*/
|
|
16
22
|
async function fetchModels(provider, baseUrl, apiKey) {
|
|
17
23
|
const base = baseUrl.replace(/\/+$/, "");
|
|
18
24
|
const resolvedKey = process.env[apiKey] ?? apiKey;
|
|
@@ -91,6 +97,11 @@ async function fetchModels(provider, baseUrl, apiKey) {
|
|
|
91
97
|
catch { /* fall through */ }
|
|
92
98
|
return { models: [] };
|
|
93
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Interactively configure API providers, detecting existing keys, allowing multi-select, and supporting custom endpoints.
|
|
102
|
+
* @param env - Current environment info with detected providers
|
|
103
|
+
* @returns Configured provider list
|
|
104
|
+
*/
|
|
94
105
|
export async function setupProviders(env) {
|
|
95
106
|
const entries = Object.entries(PROVIDERS);
|
|
96
107
|
// Detect existing providers — offer skip or add new
|
|
@@ -175,6 +186,10 @@ export async function setupProviders(env) {
|
|
|
175
186
|
}
|
|
176
187
|
return configs;
|
|
177
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Interactively configure a custom provider (Ollama, vLLM, or other OpenAI-compatible endpoints).
|
|
191
|
+
* @returns Custom provider config, or null if cancelled
|
|
192
|
+
*/
|
|
178
193
|
async function setupCustomProvider() {
|
|
179
194
|
const name = await p.text({
|
|
180
195
|
message: t("provider.name"),
|
|
@@ -207,6 +222,15 @@ async function setupCustomProvider() {
|
|
|
207
222
|
p.log.success(t("provider.customConfigured", { name, url: baseUrl }));
|
|
208
223
|
return { name, apiKey, defaultModel, baseUrl, api, discoveredModels };
|
|
209
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Select a default model by dynamically fetching available models, falling back to a static list or manual input.
|
|
227
|
+
* @param provider - Provider name
|
|
228
|
+
* @param label - Provider display label
|
|
229
|
+
* @param staticModels - Static model list fallback
|
|
230
|
+
* @param baseUrl - API base URL
|
|
231
|
+
* @param apiKey - API key
|
|
232
|
+
* @returns Selected model and discovered model metadata
|
|
233
|
+
*/
|
|
210
234
|
async function selectModelWithMeta(provider, label, staticModels, baseUrl, apiKey) {
|
|
211
235
|
let modelIds = staticModels;
|
|
212
236
|
let discoveredModels;
|
|
@@ -246,6 +270,11 @@ async function selectModelWithMeta(provider, label, staticModels, baseUrl, apiKe
|
|
|
246
270
|
}
|
|
247
271
|
return { defaultModel: model, discoveredModels, api };
|
|
248
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Prompt the user to enter an API key.
|
|
275
|
+
* @param label - Provider display label
|
|
276
|
+
* @returns The entered API key
|
|
277
|
+
*/
|
|
249
278
|
async function promptKey(label) {
|
|
250
279
|
const key = await p.password({
|
|
251
280
|
message: t("provider.apiKey", { label }),
|
package/dist/tui/theme-select.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { t } from "../i18n.js";
|
|
3
3
|
import { THEMES } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Prompts the user to select a theme from the available themes list.
|
|
6
|
+
* Exits the process if the user cancels the selection.
|
|
7
|
+
* @returns The name of the selected theme.
|
|
8
|
+
*/
|
|
4
9
|
export async function selectTheme() {
|
|
5
10
|
const theme = await p.select({
|
|
6
11
|
message: t("theme.select"),
|
package/dist/tui/welcome.d.ts
CHANGED
package/dist/tui/welcome.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { t } from "../i18n.js";
|
|
4
|
+
/**
|
|
5
|
+
* 展示欢迎界面,显示 pi 安装状态、环境信息及已有配置概况。
|
|
6
|
+
* @param {EnvInfo} env - 当前检测到的环境信息
|
|
7
|
+
*/
|
|
4
8
|
export function welcome(env) {
|
|
5
9
|
console.clear();
|
|
6
10
|
p.intro(chalk.cyan.bold(" oh-pi ") + chalk.dim(t("welcome.title")));
|
|
@@ -19,6 +23,11 @@ export function welcome(env) {
|
|
|
19
23
|
categorize(env.existingFiles), t("welcome.existingConfig"));
|
|
20
24
|
}
|
|
21
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* 按顶层目录分类统计文件数量,返回格式化字符串。
|
|
28
|
+
* @param {string[]} files - 文件相对路径列表
|
|
29
|
+
* @returns {string} 分类统计字符串,如 "extensions (3) prompts (5)"
|
|
30
|
+
*/
|
|
22
31
|
function categorize(files) {
|
|
23
32
|
const cats = {};
|
|
24
33
|
for (const f of files) {
|
package/dist/utils/resources.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join, dirname } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
7
7
|
const RESOURCES = join(PKG_ROOT, "pi-package");
|
|
8
|
+
/** 资源路径映射对象 */
|
|
8
9
|
export const resources = {
|
|
9
10
|
agent: (name) => join(RESOURCES, "agents", `${name}.md`),
|
|
10
11
|
extension: (name) => join(RESOURCES, "extensions", name),
|
package/package.json
CHANGED
|
@@ -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
|
|
19
|
+
2. If colony-worthy → use `ant_colony` tool with clear goal
|
|
20
20
|
3. If simple → do it directly
|
|
21
21
|
4. Review colony output, fix gaps manually if needed
|
|
22
22
|
|
|
@@ -36,16 +36,15 @@
|
|
|
36
36
|
|
|
37
37
|
## 使用方式
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### 使用方式
|
|
40
40
|
|
|
41
|
-
LLM
|
|
41
|
+
LLM 在判断任务复杂度足够时会自动调用 `ant_colony` tool,无需手动触发。
|
|
42
42
|
|
|
43
43
|
### 命令
|
|
44
44
|
|
|
45
45
|
```
|
|
46
|
-
/colony
|
|
47
|
-
|
|
48
|
-
Ctrl+Alt+A 快捷启动
|
|
46
|
+
/colony-stop 中止运行中的蚁群
|
|
47
|
+
Ctrl+Shift+A 展开蚁群详情面板
|
|
49
48
|
```
|
|
50
49
|
|
|
51
50
|
### 示例
|
|
@@ -114,4 +113,4 @@ npx oh-pi # 选择 Full Power 预设
|
|
|
114
113
|
| `concurrency.ts` | 115 | 自适应并发:系统采样,探索/稳态双阶段调节 |
|
|
115
114
|
| `spawner.ts` | 316 | 蚂蚁孵化:进程管理,prompt 构建,输出解析 |
|
|
116
115
|
| `queen.ts` | 331 | 女王调度:生命周期,任务波次,多轮迭代 |
|
|
117
|
-
| `index.ts` | 324 | 扩展入口:tool/
|
|
116
|
+
| `index.ts` | 324 | 扩展入口:tool/shortcut 注册,TUI 渲染 |
|
|
@@ -67,7 +67,7 @@ export function adapt(config: ConcurrencyConfig, pendingTasks: number): Concurre
|
|
|
67
67
|
// 不超过待处理任务数
|
|
68
68
|
const taskCap = Math.min(pendingTasks, config.max);
|
|
69
69
|
|
|
70
|
-
if (samples.length <
|
|
70
|
+
if (samples.length < 2) {
|
|
71
71
|
// 冷启动:直接给一半 max,快速利用并发
|
|
72
72
|
next.current = Math.min(Math.ceil(config.max / 2), taskCap);
|
|
73
73
|
return next;
|
|
@@ -111,5 +111,10 @@ export function adapt(config: ConcurrencyConfig, pendingTasks: number): Concurre
|
|
|
111
111
|
}
|
|
112
112
|
// 否则保持不变
|
|
113
113
|
|
|
114
|
+
// 429 recovery: restore to optimal when CPU is underutilized (e.g. after backoff)
|
|
115
|
+
if (latest.cpuLoad < 0.5 && next.current < config.optimal) {
|
|
116
|
+
next.current = config.optimal;
|
|
117
|
+
}
|
|
118
|
+
|
|
114
119
|
return next;
|
|
115
120
|
}
|
|
@@ -467,37 +467,163 @@ export default function antColonyExtension(pi: ExtensionAPI) {
|
|
|
467
467
|
},
|
|
468
468
|
});
|
|
469
469
|
|
|
470
|
-
// ═══
|
|
471
|
-
pi.
|
|
472
|
-
|
|
470
|
+
// ═══ Tool: ant_colony ═══
|
|
471
|
+
pi.registerTool({
|
|
472
|
+
name: "ant_colony",
|
|
473
|
+
label: "Ant Colony",
|
|
474
|
+
description: [
|
|
475
|
+
"Launch an autonomous ant colony in the BACKGROUND to accomplish a complex goal.",
|
|
476
|
+
"The colony runs asynchronously — you can continue chatting while it works.",
|
|
477
|
+
"Results are automatically injected when the colony finishes.",
|
|
478
|
+
"Scouts explore the codebase, workers execute tasks in parallel, soldiers review quality.",
|
|
479
|
+
"Use for multi-file changes, large refactors, or complex features.",
|
|
480
|
+
].join(" "),
|
|
473
481
|
parameters: Type.Object({
|
|
474
482
|
goal: Type.String({ description: "What the colony should accomplish" }),
|
|
483
|
+
maxAnts: Type.Optional(Type.Number({ description: "Max concurrent ants (default: auto-adapt)", minimum: 1, maximum: 8 })),
|
|
484
|
+
maxCost: Type.Optional(Type.Number({ description: "Max cost budget in USD (default: unlimited)", minimum: 0.01 })),
|
|
485
|
+
scoutModel: Type.Optional(Type.String({ description: "Model for scout ants (default: current session model)" })),
|
|
486
|
+
workerModel: Type.Optional(Type.String({ description: "Model for worker ants (default: current session model)" })),
|
|
487
|
+
soldierModel: Type.Optional(Type.String({ description: "Model for soldier ants (default: current session model)" })),
|
|
475
488
|
}),
|
|
476
|
-
|
|
489
|
+
|
|
490
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
491
|
+
if (activeColony) {
|
|
492
|
+
return {
|
|
493
|
+
content: [{ type: "text", text: "A colony is already running in the background. Use /colony-stop to cancel it first." }],
|
|
494
|
+
isError: true,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
477
498
|
const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null;
|
|
478
499
|
if (!currentModel) {
|
|
479
|
-
|
|
480
|
-
|
|
500
|
+
return {
|
|
501
|
+
content: [{ type: "text", text: "Colony failed: no model available in current session" }],
|
|
502
|
+
isError: true,
|
|
503
|
+
};
|
|
481
504
|
}
|
|
482
505
|
|
|
506
|
+
const modelOverrides: Record<string, string> = {};
|
|
507
|
+
if (params.scoutModel) modelOverrides.scout = params.scoutModel;
|
|
508
|
+
if (params.workerModel) modelOverrides.worker = params.workerModel;
|
|
509
|
+
if (params.soldierModel) modelOverrides.soldier = params.soldierModel;
|
|
510
|
+
|
|
483
511
|
const colonyParams = {
|
|
484
|
-
goal:
|
|
512
|
+
goal: params.goal,
|
|
513
|
+
maxAnts: params.maxAnts,
|
|
514
|
+
maxCost: params.maxCost,
|
|
485
515
|
currentModel,
|
|
486
|
-
modelOverrides
|
|
516
|
+
modelOverrides,
|
|
487
517
|
cwd: ctx.cwd,
|
|
488
518
|
modelRegistry: ctx.modelRegistry ?? undefined,
|
|
489
519
|
};
|
|
490
520
|
|
|
521
|
+
// 非交互模式(print mode):同步等待蚁群完成
|
|
491
522
|
if (!ctx.hasUI) {
|
|
492
|
-
|
|
493
|
-
if (result.isError) {
|
|
494
|
-
ctx.ui.notify("Colony failed", "error");
|
|
495
|
-
}
|
|
496
|
-
return;
|
|
523
|
+
return await runSyncColony(colonyParams, _signal);
|
|
497
524
|
}
|
|
498
525
|
|
|
526
|
+
// 交互模式:后台运行
|
|
499
527
|
launchBackgroundColony(colonyParams);
|
|
500
|
-
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
content: [{ type: "text", text: `[COLONY_SIGNAL:LAUNCHED]\n🐜 Colony launched in background.\nGoal: ${params.goal}\n\nThe colony is now running autonomously. Results will be injected when it finishes.` }],
|
|
531
|
+
};
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
renderCall(args, theme) {
|
|
535
|
+
const goal = args.goal?.length > 70 ? args.goal.slice(0, 67) + "..." : args.goal;
|
|
536
|
+
let text = theme.fg("toolTitle", theme.bold("🐜 ant_colony"));
|
|
537
|
+
if (args.maxAnts) text += theme.fg("muted", ` ×${args.maxAnts}`);
|
|
538
|
+
if (args.maxCost) text += theme.fg("warning", ` $${args.maxCost}`);
|
|
539
|
+
text += "\n" + theme.fg("dim", ` ${goal || "..."}`);
|
|
540
|
+
return new Text(text, 0, 0);
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
renderResult(result, { expanded }, theme) {
|
|
544
|
+
const text = result.content?.find((c: any) => c.type === "text")?.text || "";
|
|
545
|
+
if (result.isError) {
|
|
546
|
+
return new Text(theme.fg("error", text), 0, 0);
|
|
547
|
+
}
|
|
548
|
+
const container = new Container();
|
|
549
|
+
container.addChild(new Text(
|
|
550
|
+
theme.fg("success", "✓ ") + theme.fg("toolTitle", theme.bold("Colony launched in background")),
|
|
551
|
+
0, 0,
|
|
552
|
+
));
|
|
553
|
+
if (activeColony) {
|
|
554
|
+
container.addChild(new Text(theme.fg("dim", ` Goal: ${activeColony.goal.slice(0, 70)}`), 0, 0));
|
|
555
|
+
container.addChild(new Text(theme.fg("muted", ` Ctrl+Shift+A for details │ /colony-stop to cancel`), 0, 0));
|
|
556
|
+
}
|
|
557
|
+
return container;
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// ═══ Helper: build status summary ═══
|
|
562
|
+
|
|
563
|
+
function buildStatusText(): string {
|
|
564
|
+
if (!activeColony) return "No colony is currently running.";
|
|
565
|
+
const c = activeColony;
|
|
566
|
+
const state = c.state;
|
|
567
|
+
const elapsed = state ? formatDuration(Date.now() - state.createdAt) : "0s";
|
|
568
|
+
const m = state?.metrics;
|
|
569
|
+
const tasks = state?.tasks || [];
|
|
570
|
+
const ants = state?.ants || [];
|
|
571
|
+
const streams = Array.from(c.antStreams.values());
|
|
572
|
+
|
|
573
|
+
const lines: string[] = [
|
|
574
|
+
`## 🐜 Colony Status`,
|
|
575
|
+
`**Goal:** ${c.goal}`,
|
|
576
|
+
`**Phase:** ${c.phase}`,
|
|
577
|
+
`**Duration:** ${elapsed}`,
|
|
578
|
+
];
|
|
579
|
+
|
|
580
|
+
if (m) {
|
|
581
|
+
lines.push(`**Tasks:** ${m.tasksDone}/${m.tasksTotal} done, ${m.tasksFailed} failed`);
|
|
582
|
+
lines.push(`**Ants spawned:** ${m.antsSpawned} | **Active:** ${streams.length}`);
|
|
583
|
+
lines.push(`**Cost:** ${formatCost(m.totalCost)} | **Tokens:** ${formatTokens(m.totalTokens)}`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (tasks.length > 0) {
|
|
587
|
+
lines.push("", "### Tasks");
|
|
588
|
+
for (const t of tasks) {
|
|
589
|
+
const icon = t.status === "done" ? "✓" : t.status === "failed" ? "✗" : t.status === "active" ? "●" : "○";
|
|
590
|
+
const dur = t.finishedAt && t.startedAt ? ` (${formatDuration(t.finishedAt - t.startedAt)})` : "";
|
|
591
|
+
lines.push(`- ${icon} [${t.caste}] ${t.title}${dur}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (streams.length > 0) {
|
|
596
|
+
lines.push("", "### Active Ants");
|
|
597
|
+
for (const s of streams) {
|
|
598
|
+
lines.push(`- ${casteIcon(s.caste)} ${s.antId.slice(0, 14)} | ${s.tokens}tok | ${s.lastLine.slice(0, 60)}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return lines.join("\n");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ═══ Tool: bg_colony_status ═══
|
|
606
|
+
pi.registerTool({
|
|
607
|
+
name: "bg_colony_status",
|
|
608
|
+
label: "Colony Status",
|
|
609
|
+
description: "Check the status of a running background ant colony. Use this instead of bg_status to monitor colony progress.",
|
|
610
|
+
parameters: Type.Object({}),
|
|
611
|
+
async execute() {
|
|
612
|
+
return {
|
|
613
|
+
content: [{ type: "text" as const, text: buildStatusText() }],
|
|
614
|
+
};
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// ═══ Command: /colony-status ═══
|
|
619
|
+
pi.registerCommand("colony-status", {
|
|
620
|
+
description: "Show current colony progress",
|
|
621
|
+
async handler(_args, ctx) {
|
|
622
|
+
if (!activeColony) {
|
|
623
|
+
ctx.ui.notify("No colony is currently running.", "info");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
ctx.ui.notify(buildStatusText(), "info");
|
|
501
627
|
},
|
|
502
628
|
});
|
|
503
629
|
|
|
@@ -226,8 +226,8 @@ export class Nest {
|
|
|
226
226
|
// ═══ Internal ═══
|
|
227
227
|
|
|
228
228
|
private withStateLock<T>(fn: () => T): T {
|
|
229
|
-
const MAX_WAIT =
|
|
230
|
-
const SPIN_MS =
|
|
229
|
+
const MAX_WAIT = 3000;
|
|
230
|
+
const SPIN_MS = 1;
|
|
231
231
|
const start = Date.now();
|
|
232
232
|
while (true) {
|
|
233
233
|
try {
|
|
@@ -290,6 +290,9 @@ async function runAntWave(opts: WaveOptions): Promise<"ok"> {
|
|
|
290
290
|
* 蚁后主循环
|
|
291
291
|
*/
|
|
292
292
|
export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
293
|
+
if (!opts.goal || !opts.goal.trim()) {
|
|
294
|
+
throw new Error("Colony goal is empty or undefined. Please provide a clear goal.");
|
|
295
|
+
}
|
|
293
296
|
const colonyId = makeColonyId();
|
|
294
297
|
const nest = new Nest(opts.cwd, colonyId);
|
|
295
298
|
|
|
@@ -375,7 +378,6 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
375
378
|
nest.updateState({ status: "failed", finishedAt: Date.now() });
|
|
376
379
|
const finalState = nest.getState();
|
|
377
380
|
callbacks.onComplete(finalState);
|
|
378
|
-
cleanup();
|
|
379
381
|
return finalState;
|
|
380
382
|
}
|
|
381
383
|
|
|
@@ -421,14 +423,14 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
421
423
|
nest.updateState({ status: "done", finishedAt: Date.now(), metrics: finalMetrics });
|
|
422
424
|
const finalState = nest.getState();
|
|
423
425
|
callbacks.onComplete(finalState);
|
|
424
|
-
cleanup();
|
|
425
426
|
return finalState;
|
|
426
427
|
|
|
427
428
|
} catch (e) {
|
|
428
429
|
nest.updateState({ status: "failed", finishedAt: Date.now() });
|
|
429
430
|
const failState = nest.getState();
|
|
430
431
|
callbacks.onComplete(failState);
|
|
431
|
-
cleanup();
|
|
432
432
|
return failState;
|
|
433
|
+
} finally {
|
|
434
|
+
cleanup();
|
|
433
435
|
}
|
|
434
436
|
}
|
|
@@ -142,6 +142,9 @@ function buildPrompt(task: Task, pheromoneContext: string, castePrompt: string,
|
|
|
142
142
|
if (task.files.length > 0) {
|
|
143
143
|
prompt += `**Files scope:** ${task.files.join(", ")}\n`;
|
|
144
144
|
}
|
|
145
|
+
if (/[\u4e00-\u9fff]/.test(task.description)) {
|
|
146
|
+
prompt += '\nIMPORTANT: Follow the language requirements specified in the task description. If the task says to write in Chinese, write in Chinese.\n';
|
|
147
|
+
}
|
|
145
148
|
return prompt;
|
|
146
149
|
}
|
|
147
150
|
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ant-colony
|
|
3
|
-
description: Ant colony multi-agent orchestration. Use when user needs parallel multi-file changes, large refactors, or complex features. Provides colony management commands and strategies.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Ant Colony Skill
|
|
7
|
-
|
|
8
|
-
## When to Use
|
|
9
|
-
- Multi-file changes (≥3 files)
|
|
10
|
-
- Parallel workstreams
|
|
11
|
-
- Large refactors, migrations
|
|
12
|
-
- User says: colony, swarm, parallel, multi-agent
|
|
13
|
-
|
|
14
|
-
## Quick Start
|
|
15
|
-
```
|
|
16
|
-
/colony <goal>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Colony Lifecycle
|
|
20
|
-
1. **Scout** — Explore codebase, identify targets, produce task pool
|
|
21
|
-
2. **Work** — Workers execute in parallel, adaptive concurrency
|
|
22
|
-
3. **Review** — Soldiers audit changes, request fixes if needed
|
|
23
|
-
4. **Fix** — Workers address review findings
|
|
24
|
-
5. **Done** — Summary report
|
|
25
|
-
|
|
26
|
-
## Strategies
|
|
27
|
-
|
|
28
|
-
### File-Scoped Decomposition (default)
|
|
29
|
-
Each worker owns distinct files. Zero conflict.
|
|
30
|
-
```
|
|
31
|
-
Worker A: [src/auth.ts, src/auth.test.ts]
|
|
32
|
-
Worker B: [src/api.ts, src/api.test.ts]
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Module-Scoped
|
|
36
|
-
Each worker owns a module directory.
|
|
37
|
-
```
|
|
38
|
-
Worker A: src/components/
|
|
39
|
-
Worker B: src/api/
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Pipeline
|
|
43
|
-
Sequential handoff when dependencies exist.
|
|
44
|
-
```
|
|
45
|
-
Scout → Worker(schema) → Worker(api) → Worker(ui) → Soldier
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Tuning
|
|
49
|
-
- `maxAnts`: Cap concurrent ants (default: auto-adapt to CPU)
|
|
50
|
-
- Colony auto-reduces concurrency on 429 rate limits
|
|
51
|
-
- Blocked tasks auto-resume when file locks release
|
|
52
|
-
|
|
53
|
-
## Extending Castes
|
|
54
|
-
Add custom ant types by editing `ant-colony/types.ts`:
|
|
55
|
-
```typescript
|
|
56
|
-
// In DEFAULT_ANT_CONFIGS, add:
|
|
57
|
-
mycaste: { caste: "mycaste", model: "...", tools: [...], maxTurns: 10 }
|
|
58
|
-
```
|
|
59
|
-
Then add matching prompt in `spawner.ts` CASTE_PROMPTS.
|