@ifi/oh-pi-ant-colony 0.2.12 → 0.2.14

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/README.md CHANGED
@@ -124,10 +124,10 @@ pi install npm:@ifi/oh-pi-ant-colony
124
124
  pi install npm:@ifi/oh-pi
125
125
  ```
126
126
 
127
- Then enable/configure extensions with:
127
+ Then start pi:
128
128
 
129
129
  ```bash
130
- npx @ifi/oh-pi-cli
130
+ pi
131
131
  ```
132
132
 
133
133
  ## Module Reference
@@ -183,7 +183,7 @@ export default function antColonyExtension(pi: ExtensionAPI) {
183
183
 
184
184
  const isExplicitStatusRequest = (ctx: unknown): boolean => {
185
185
  const text = lastUserMessageText(ctx);
186
- return /(?:\/colony-status|bg_colony_status)|(?:(?:蚁群|colony).{0,20}(?:状态|进度|进展|汇报|快照|status|progress|snapshot|update|check))|(?:(?:状态|进度|进展|汇报|快照|status|progress|snapshot|update|check).{0,20}(?:蚁群|colony))/i.test(
186
+ return /(?:\/colony-status|bg_colony_status)|(?:colony.{0,20}(?:status|progress|snapshot|update|check))|(?:(?:status|progress|snapshot|update|check).{0,20}colony)/i.test(
187
187
  text,
188
188
  );
189
189
  };
@@ -2,7 +2,7 @@ import { makePheromoneId } from "./spawner.js";
2
2
  import type { AntCaste, Pheromone, PheromoneType } from "./types.js";
3
3
 
4
4
  const VALID_CASTES = new Set(["scout", "worker", "soldier", "drone"]);
5
- const TASK_HEADER_RE = /^\s*#{2,6}\s*(?:task|任务)\s*[::]\s*(.+?)\s*$/i;
5
+ const TASK_HEADER_RE = /^\s*#{2,6}\s*task\s*:\s*(.+?)\s*$/i;
6
6
 
7
7
  export interface ParsedSubTask {
8
8
  title: string;
@@ -25,13 +25,13 @@ function normalizeCaste(v: unknown): AntCaste {
25
25
  if (VALID_CASTES.has(raw)) {
26
26
  return raw as AntCaste;
27
27
  }
28
- if (raw.includes("侦察") || raw.includes("scout")) {
28
+ if (raw.includes("scout")) {
29
29
  return "scout";
30
30
  }
31
- if (raw.includes("工") || raw.includes("worker")) {
31
+ if (raw.includes("worker")) {
32
32
  return "worker";
33
33
  }
34
- if (raw.includes("兵") || raw.includes("review") || raw.includes("soldier")) {
34
+ if (raw.includes("review") || raw.includes("soldier")) {
35
35
  return "soldier";
36
36
  }
37
37
  if (raw.includes("drone") || raw.includes("bash") || raw.includes("shell")) {
@@ -41,10 +41,7 @@ function normalizeCaste(v: unknown): AntCaste {
41
41
  }
42
42
 
43
43
  function extractFileLike(value: string): string[] {
44
- const normalized = value
45
- .replace(/[,、;;]/g, ",")
46
- .replace(/["']/g, "")
47
- .replace(/`/g, "");
44
+ const normalized = value.replace(/;/g, ",").replace(/["']/g, "").replace(/`/g, "");
48
45
  const tokens = normalized
49
46
  .split(",")
50
47
  .map((s) => s.trim())
@@ -53,21 +50,49 @@ function extractFileLike(value: string): string[] {
53
50
  return [...new Set(fileish)];
54
51
  }
55
52
 
53
+ function isRecord(value: unknown): value is Record<string, unknown> {
54
+ return typeof value === "object" && value !== null;
55
+ }
56
+
57
+ function hasNonEmptyText(value: unknown): boolean {
58
+ return typeof value === "string" && value.trim().length > 0;
59
+ }
60
+
61
+ function isJsonTaskLike(value: unknown): value is Record<string, unknown> {
62
+ return isRecord(value) && (hasNonEmptyText(value.title) || hasNonEmptyText(value.description));
63
+ }
64
+
65
+ function extractJsonTaskCandidates(parsed: unknown): Array<Record<string, unknown>> {
66
+ if (Array.isArray(parsed)) {
67
+ return parsed.filter(isJsonTaskLike);
68
+ }
69
+ if (isRecord(parsed) && Array.isArray(parsed.tasks)) {
70
+ return parsed.tasks.filter(isJsonTaskLike);
71
+ }
72
+ if (isJsonTaskLike(parsed)) {
73
+ return [parsed];
74
+ }
75
+ return [];
76
+ }
77
+
56
78
  function normalizeJsonTasks(parsed: unknown): ParsedSubTask[] {
57
- const arr = (Array.isArray(parsed) ? parsed : [parsed]) as Array<Record<string, unknown>>;
58
- return arr.map((t) => ({
59
- title: String(t.title || "Untitled"),
60
- description: String(t.description || t.title || ""),
61
- files: Array.isArray(t.files)
62
- ? t.files
63
- .map(String)
64
- .map((f) => f.trim())
65
- .filter(Boolean)
66
- : extractFileLike(String(t.files || "")),
67
- caste: normalizeCaste(t.caste),
68
- priority: normalizePriority(t.priority),
69
- context: t.context ? String(t.context) : undefined,
70
- }));
79
+ return extractJsonTaskCandidates(parsed).map((t) => {
80
+ const title = String(t.title || t.description || "Untitled").trim() || "Untitled";
81
+ const description = String(t.description || t.title || title).trim() || title;
82
+ return {
83
+ title,
84
+ description,
85
+ files: Array.isArray(t.files)
86
+ ? t.files
87
+ .map(String)
88
+ .map((f) => f.trim())
89
+ .filter(Boolean)
90
+ : extractFileLike(String(t.files || "")),
91
+ caste: normalizeCaste(t.caste),
92
+ priority: normalizePriority(t.priority),
93
+ context: t.context ? String(t.context) : undefined,
94
+ };
95
+ });
71
96
  }
72
97
 
73
98
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Parser must handle many field variants (en/zh) and edge cases
@@ -95,7 +120,7 @@ function parseTasksFromStructuredLines(output: string): ParsedSubTask[] {
95
120
 
96
121
  const fieldMatch = (line: string) => {
97
122
  return line.match(
98
- /^\s*(?:[-*]|\d+\.)?\s*(?:\*\*|__)?\s*(description|desc|描述|说明|files?|文件|路径|caste|role|角色|priority|prio|优先级|context|上下文)\s*(?:\*\*|__)?\s*[::]\s*(.*)$/i,
123
+ /^\s*(?:[-*]|\d+\.)?\s*(?:\*\*|__)?\s*(description|desc|files?|caste|role|priority|prio|context)\s*(?:\*\*|__)?\s*:\s*(.*)$/i,
99
124
  );
100
125
  };
101
126
 
@@ -127,27 +152,27 @@ function parseTasksFromStructuredLines(output: string): ParsedSubTask[] {
127
152
  const key = m[1].toLowerCase();
128
153
  const value = (m[2] || "").trim();
129
154
 
130
- if (["description", "desc", "描述", "说明"].includes(key)) {
155
+ if (["description", "desc"].includes(key)) {
131
156
  current.description = value;
132
157
  continue;
133
158
  }
134
159
 
135
- if (["files", "file", "文件", "路径"].includes(key)) {
160
+ if (["files", "file"].includes(key)) {
136
161
  current.files.push(...extractFileLike(value));
137
162
  continue;
138
163
  }
139
164
 
140
- if (["caste", "role", "角色"].includes(key)) {
165
+ if (["caste", "role"].includes(key)) {
141
166
  current.caste = normalizeCaste(value);
142
167
  continue;
143
168
  }
144
169
 
145
- if (["priority", "prio", "优先级"].includes(key)) {
170
+ if (["priority", "prio"].includes(key)) {
146
171
  current.priority = normalizePriority(value);
147
172
  continue;
148
173
  }
149
174
 
150
- if (["context", "上下文"].includes(key)) {
175
+ if (key === "context") {
151
176
  const contextLines = [value];
152
177
  while (i + 1 < lines.length) {
153
178
  const next = lines[i + 1];
@@ -173,13 +198,16 @@ export function parseSubTasks(output: string): ParsedSubTask[] {
173
198
  const jsonMatch = output.match(/```json\s*([\s\S]*?)```/i);
174
199
  if (jsonMatch?.[1]) {
175
200
  try {
176
- return normalizeJsonTasks(JSON.parse(jsonMatch[1].trim()));
201
+ const jsonTasks = normalizeJsonTasks(JSON.parse(jsonMatch[1].trim()));
202
+ if (jsonTasks.length > 0) {
203
+ return jsonTasks;
204
+ }
177
205
  } catch {
178
206
  /* fallback */
179
207
  }
180
208
  }
181
209
 
182
- // 2) Structured markdown task blocks (English/Chinese)
210
+ // 2) Structured markdown task blocks
183
211
  return parseTasksFromStructuredLines(output);
184
212
  }
185
213
 
@@ -107,9 +107,5 @@ export function buildPrompt(
107
107
  if (task.context) {
108
108
  prompt += `\n## Pre-loaded Context (from scout)\n${task.context}\n`;
109
109
  }
110
- if (/[\u4e00-\u9fff]/.test(task.description)) {
111
- prompt +=
112
- "\nIMPORTANT: Follow the language requirements specified in the task description. If the task says to write in Chinese, write in Chinese.\n";
113
- }
114
110
  return prompt;
115
111
  }
@@ -259,7 +259,7 @@ export interface PlanValidation {
259
259
 
260
260
  export function shouldUseScoutQuorum(goal: string): boolean {
261
261
  // Multi-step/compound goals benefit from at least 2 scout votes
262
- return /(\n\s*\d+[.)]|[;;]| and |以及|并且|同时|步骤|phase|then|之后)/i.test(goal);
262
+ return /(\n\s*\d+[.)]|;| and |phase|then)/i.test(goal);
263
263
  }
264
264
 
265
265
  export function decidePromoteOrFinalize(input: PromoteFinalizeGateInput): PromoteFinalizeGateDecision {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifi/oh-pi-ant-colony",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Autonomous multi-agent swarm extension for pi — adaptive concurrency, pheromone communication.",
5
5
  "keywords": [
6
6
  "pi-package"