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 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>;
@@ -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
- /** Fetch models dynamically — tries multiple API styles, returns metadata + detected API type */
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 }),
@@ -1 +1,6 @@
1
+ /**
2
+ * Prompts the user to select a theme from the available themes list.
3
+ * Exits the process if the user cancels the selection.
4
+ * @returns The name of the selected theme.
5
+ */
1
6
  export declare function selectTheme(): Promise<string>;
@@ -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"),
@@ -1,2 +1,6 @@
1
1
  import type { EnvInfo } from "../utils/detect.js";
2
+ /**
3
+ * 展示欢迎界面,显示 pi 安装状态、环境信息及已有配置概况。
4
+ * @param {EnvInfo} env - 当前检测到的环境信息
5
+ */
2
6
  export declare function welcome(env: EnvInfo): void;
@@ -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) {
@@ -1,3 +1,4 @@
1
+ /** 资源路径映射对象 */
1
2
  export declare const resources: {
2
3
  agent: (name: string) => string;
3
4
  extension: (name: string) => string;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-pi",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 < 3) {
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 = 5000;
230
- const SPIN_MS = 15;
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;
@@ -35,6 +35,7 @@ export interface Task {
35
35
  status: TaskStatus;
36
36
  priority: TaskPriority;
37
37
  files: string[]; // 锁定的文件
38
+ context?: string; // Scout 预加载的代码片段
38
39
  claimedBy: string | null; // ant id
39
40
  result: string | null;
40
41
  error: string | null;