oh-pi 0.1.52 → 0.1.54
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/extensions/ant-colony/concurrency.ts +6 -1
- package/pi-package/extensions/ant-colony/index.ts +69 -0
- package/pi-package/extensions/ant-colony/nest.ts +2 -2
- package/pi-package/extensions/ant-colony/queen.ts +14 -5
- package/pi-package/extensions/ant-colony/spawner.ts +14 -0
- package/pi-package/extensions/ant-colony/types.ts +1 -0
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
|
@@ -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
|
}
|
|
@@ -558,6 +558,75 @@ export default function antColonyExtension(pi: ExtensionAPI) {
|
|
|
558
558
|
},
|
|
559
559
|
});
|
|
560
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");
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
|
|
561
630
|
// ═══ Command: /colony-stop ═══
|
|
562
631
|
pi.registerCommand("colony-stop", {
|
|
563
632
|
description: "Stop the running background colony",
|
|
@@ -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 {
|
|
@@ -72,7 +72,7 @@ function makeInitialScoutTask(goal: string): Task {
|
|
|
72
72
|
|
|
73
73
|
function childTaskFromParsed(
|
|
74
74
|
parentId: string,
|
|
75
|
-
parsed: { title: string; description: string; files: string[]; caste: AntCaste; priority: TaskPriority },
|
|
75
|
+
parsed: { title: string; description: string; files: string[]; caste: AntCaste; priority: TaskPriority; context?: string },
|
|
76
76
|
): Task {
|
|
77
77
|
return {
|
|
78
78
|
id: makeTaskId(),
|
|
@@ -83,6 +83,7 @@ function childTaskFromParsed(
|
|
|
83
83
|
status: "pending",
|
|
84
84
|
priority: parsed.priority,
|
|
85
85
|
files: parsed.files,
|
|
86
|
+
context: parsed.context || undefined,
|
|
86
87
|
claimedBy: null,
|
|
87
88
|
result: null,
|
|
88
89
|
error: null,
|
|
@@ -378,7 +379,6 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
378
379
|
nest.updateState({ status: "failed", finishedAt: Date.now() });
|
|
379
380
|
const finalState = nest.getState();
|
|
380
381
|
callbacks.onComplete(finalState);
|
|
381
|
-
cleanup();
|
|
382
382
|
return finalState;
|
|
383
383
|
}
|
|
384
384
|
|
|
@@ -399,9 +399,18 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
399
399
|
await runAntWave({ ...waveBase, caste: "worker" });
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
// ═══ Auto-check: run tsc before soldier review ═══
|
|
403
|
+
let tscPassed = true;
|
|
404
|
+
try {
|
|
405
|
+
const { execSync } = await import("node:child_process");
|
|
406
|
+
execSync("npx tsc --noEmit", { cwd: opts.cwd, timeout: 30000, stdio: "pipe" });
|
|
407
|
+
} catch {
|
|
408
|
+
tscPassed = false;
|
|
409
|
+
}
|
|
410
|
+
|
|
402
411
|
// ═══ Phase 3: 审查 ═══
|
|
403
412
|
const completedWorkerTasks = nest.getAllTasks().filter(t => t.caste === "worker" && t.status === "done");
|
|
404
|
-
if (completedWorkerTasks.length > 0) {
|
|
413
|
+
if (completedWorkerTasks.length > 0 && (!tscPassed || completedWorkerTasks.length > 3)) {
|
|
405
414
|
nest.updateState({ status: "reviewing" });
|
|
406
415
|
callbacks.onPhase("reviewing", "Dispatching soldier ants to review changes...");
|
|
407
416
|
const reviewTask = makeReviewTask(completedWorkerTasks);
|
|
@@ -424,14 +433,14 @@ export async function runColony(opts: QueenOptions): Promise<ColonyState> {
|
|
|
424
433
|
nest.updateState({ status: "done", finishedAt: Date.now(), metrics: finalMetrics });
|
|
425
434
|
const finalState = nest.getState();
|
|
426
435
|
callbacks.onComplete(finalState);
|
|
427
|
-
cleanup();
|
|
428
436
|
return finalState;
|
|
429
437
|
|
|
430
438
|
} catch (e) {
|
|
431
439
|
nest.updateState({ status: "failed", finishedAt: Date.now() });
|
|
432
440
|
const failState = nest.getState();
|
|
433
441
|
callbacks.onComplete(failState);
|
|
434
|
-
cleanup();
|
|
435
442
|
return failState;
|
|
443
|
+
} finally {
|
|
444
|
+
cleanup();
|
|
436
445
|
}
|
|
437
446
|
}
|
|
@@ -57,6 +57,7 @@ export interface ParsedSubTask {
|
|
|
57
57
|
files: string[];
|
|
58
58
|
caste: AntCaste;
|
|
59
59
|
priority: 1 | 2 | 3 | 4 | 5;
|
|
60
|
+
context?: string;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const CASTE_PROMPTS: Record<AntCaste, string> = {
|
|
@@ -67,6 +68,7 @@ Behavior:
|
|
|
67
68
|
- Identify files, functions, dependencies related to the goal
|
|
68
69
|
- IMPORTANT: After EACH tool call, summarize what you found so far. Do NOT wait until the end.
|
|
69
70
|
- Report findings as structured intelligence for Worker Ants
|
|
71
|
+
- For each recommended task, include the KEY code snippets (with file:line) the worker will need — this saves workers from re-reading files
|
|
70
72
|
|
|
71
73
|
Output format (MUST follow exactly):
|
|
72
74
|
## Discoveries
|
|
@@ -79,6 +81,7 @@ For each task the colony should do next:
|
|
|
79
81
|
- files: <comma-separated file paths>
|
|
80
82
|
- caste: worker
|
|
81
83
|
- priority: <1-5, 1=highest>
|
|
84
|
+
- context: <relevant code snippets that the worker will need, with file:line references>
|
|
82
85
|
|
|
83
86
|
## Warnings
|
|
84
87
|
Any risks, blockers, or conflicts detected.`,
|
|
@@ -142,6 +145,12 @@ function buildPrompt(task: Task, pheromoneContext: string, castePrompt: string,
|
|
|
142
145
|
if (task.files.length > 0) {
|
|
143
146
|
prompt += `**Files scope:** ${task.files.join(", ")}\n`;
|
|
144
147
|
}
|
|
148
|
+
if (task.context) {
|
|
149
|
+
prompt += `\n## Pre-loaded Context (from scout)\n${task.context}\n`;
|
|
150
|
+
}
|
|
151
|
+
if (/[\u4e00-\u9fff]/.test(task.description)) {
|
|
152
|
+
prompt += '\nIMPORTANT: Follow the language requirements specified in the task description. If the task says to write in Chinese, write in Chinese.\n';
|
|
153
|
+
}
|
|
145
154
|
return prompt;
|
|
146
155
|
}
|
|
147
156
|
|
|
@@ -149,13 +158,18 @@ function buildPrompt(task: Task, pheromoneContext: string, castePrompt: string,
|
|
|
149
158
|
function parseSubTasks(output: string): ParsedSubTask[] {
|
|
150
159
|
const tasks: ParsedSubTask[] = [];
|
|
151
160
|
const regex = /### TASK:\s*(.+)\n(?:- description:\s*(.+)\n)?(?:- files:\s*(.+)\n)?(?:- caste:\s*(\w+)\n)?(?:- priority:\s*(\d))?/g;
|
|
161
|
+
const taskBlocks = output.split(/(?=### TASK:)/);
|
|
152
162
|
for (const m of output.matchAll(regex)) {
|
|
163
|
+
const block = taskBlocks.find(b => b.includes(`### TASK: ${m[1]?.trim()}`)) || "";
|
|
164
|
+
const ctxMatch = block.match(/- context:\s*([\s\S]*?)(?=### TASK:|## |\n\n|$)/);
|
|
165
|
+
const context = ctxMatch?.[1]?.trim() || undefined;
|
|
153
166
|
tasks.push({
|
|
154
167
|
title: m[1]?.trim() || "Untitled",
|
|
155
168
|
description: m[2]?.trim() || m[1]?.trim() || "",
|
|
156
169
|
files: (m[3]?.trim() || "").split(",").map(f => f.trim()).filter(Boolean),
|
|
157
170
|
caste: (m[4]?.trim() as AntCaste) || "worker",
|
|
158
171
|
priority: (parseInt(m[5] || "3") as 1 | 2 | 3 | 4 | 5) || 3,
|
|
172
|
+
context,
|
|
159
173
|
});
|
|
160
174
|
}
|
|
161
175
|
return tasks;
|