@oh-my-pi/pi-coding-agent 4.2.3 → 4.3.1
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/CHANGELOG.md +23 -0
- package/package.json +5 -5
- package/src/cli/update-cli.ts +2 -2
- package/src/config.ts +5 -5
- package/src/core/auth-storage.ts +13 -1
- package/src/core/cursor/exec-bridge.ts +234 -0
- package/src/core/custom-commands/loader.ts +3 -1
- package/src/core/custom-tools/loader.ts +1 -18
- package/src/core/extensions/loader.ts +5 -21
- package/src/core/hooks/loader.ts +1 -18
- package/src/core/keybindings.ts +3 -1
- package/src/core/logger.ts +1 -2
- package/src/core/model-resolver.ts +1 -0
- package/src/core/prompt-templates.ts +5 -4
- package/src/core/sdk.ts +17 -4
- package/src/core/skills.ts +5 -4
- package/src/core/tools/edit-diff.ts +44 -21
- package/src/core/tools/exa/mcp-client.ts +2 -2
- package/src/core/tools/task/agents.ts +5 -64
- package/src/core/tools/task/commands.ts +7 -33
- package/src/core/tools/task/discovery.ts +4 -66
- package/src/core/tools/task/executor.ts +32 -3
- package/src/core/tools/task/index.ts +11 -2
- package/src/core/tools/task/render.ts +25 -15
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/task/worker-protocol.ts +2 -1
- package/src/core/tools/task/worker.ts +2 -1
- package/src/core/tools/web-scrapers/huggingface.ts +1 -1
- package/src/core/tools/web-scrapers/readthedocs.ts +1 -1
- package/src/core/tools/web-scrapers/types.ts +1 -1
- package/src/core/tools/web-search/auth.ts +5 -3
- package/src/discovery/codex.ts +3 -1
- package/src/discovery/helpers.ts +124 -3
- package/src/migrations.ts +11 -9
- package/src/modes/interactive/components/extensions/state-manager.ts +19 -18
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/agents/reviewer.md +32 -4
- package/src/prompts/tools/task.md +3 -1
|
@@ -49,9 +49,9 @@ function countLeadingWhitespace(line: string): number {
|
|
|
49
49
|
const char = line[i];
|
|
50
50
|
if (char === " " || char === "\t") {
|
|
51
51
|
count++;
|
|
52
|
-
|
|
52
|
+
} else {
|
|
53
|
+
break;
|
|
53
54
|
}
|
|
54
|
-
break;
|
|
55
55
|
}
|
|
56
56
|
return count;
|
|
57
57
|
}
|
|
@@ -80,15 +80,16 @@ function computeRelativeIndentDepths(lines: string[]): number[] {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function normalizeLinesForMatch(lines: string[]): string[] {
|
|
84
|
-
const indentDepths = computeRelativeIndentDepths(lines);
|
|
83
|
+
function normalizeLinesForMatch(lines: string[], includeDepth = true): string[] {
|
|
84
|
+
const indentDepths = includeDepth ? computeRelativeIndentDepths(lines) : null;
|
|
85
85
|
return lines.map((line, index) => {
|
|
86
86
|
const trimmed = line.trim();
|
|
87
|
+
const prefix = indentDepths ? `${indentDepths[index]}|` : "|";
|
|
87
88
|
if (trimmed.length === 0) {
|
|
88
|
-
return
|
|
89
|
+
return prefix;
|
|
89
90
|
}
|
|
90
91
|
const collapsed = trimmed.replace(/[ \t]+/g, " ");
|
|
91
|
-
return `${
|
|
92
|
+
return `${prefix}${collapsed}`;
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
95
|
|
|
@@ -148,22 +149,14 @@ function computeLineOffsets(lines: string[]): number[] {
|
|
|
148
149
|
return offsets;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
function findBestFuzzyMatchCore(
|
|
153
|
+
contentLines: string[],
|
|
154
|
+
targetLines: string[],
|
|
155
|
+
offsets: number[],
|
|
154
156
|
threshold: number,
|
|
157
|
+
includeDepth: boolean,
|
|
155
158
|
): { best?: EditMatch; aboveThresholdCount: number } {
|
|
156
|
-
const
|
|
157
|
-
const targetLines = target.split("\n");
|
|
158
|
-
if (targetLines.length === 0 || target.length === 0) {
|
|
159
|
-
return { aboveThresholdCount: 0 };
|
|
160
|
-
}
|
|
161
|
-
if (targetLines.length > contentLines.length) {
|
|
162
|
-
return { aboveThresholdCount: 0 };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const targetNormalized = normalizeLinesForMatch(targetLines);
|
|
166
|
-
const offsets = computeLineOffsets(contentLines);
|
|
159
|
+
const targetNormalized = normalizeLinesForMatch(targetLines, includeDepth);
|
|
167
160
|
|
|
168
161
|
let best: EditMatch | undefined;
|
|
169
162
|
let bestScore = -1;
|
|
@@ -171,7 +164,7 @@ function findBestFuzzyMatch(
|
|
|
171
164
|
|
|
172
165
|
for (let start = 0; start <= contentLines.length - targetLines.length; start++) {
|
|
173
166
|
const windowLines = contentLines.slice(start, start + targetLines.length);
|
|
174
|
-
const windowNormalized = normalizeLinesForMatch(windowLines);
|
|
167
|
+
const windowNormalized = normalizeLinesForMatch(windowLines, includeDepth);
|
|
175
168
|
let score = 0;
|
|
176
169
|
for (let i = 0; i < targetLines.length; i++) {
|
|
177
170
|
score += similarityScore(targetNormalized[i], windowNormalized[i]);
|
|
@@ -196,6 +189,36 @@ function findBestFuzzyMatch(
|
|
|
196
189
|
return { best, aboveThresholdCount };
|
|
197
190
|
}
|
|
198
191
|
|
|
192
|
+
const FALLBACK_THRESHOLD = 0.8;
|
|
193
|
+
|
|
194
|
+
function findBestFuzzyMatch(
|
|
195
|
+
content: string,
|
|
196
|
+
target: string,
|
|
197
|
+
threshold: number,
|
|
198
|
+
): { best?: EditMatch; aboveThresholdCount: number } {
|
|
199
|
+
const contentLines = content.split("\n");
|
|
200
|
+
const targetLines = target.split("\n");
|
|
201
|
+
if (targetLines.length === 0 || target.length === 0) {
|
|
202
|
+
return { aboveThresholdCount: 0 };
|
|
203
|
+
}
|
|
204
|
+
if (targetLines.length > contentLines.length) {
|
|
205
|
+
return { aboveThresholdCount: 0 };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const offsets = computeLineOffsets(contentLines);
|
|
209
|
+
|
|
210
|
+
let result = findBestFuzzyMatchCore(contentLines, targetLines, offsets, threshold, true);
|
|
211
|
+
|
|
212
|
+
if (result.best && result.best.confidence < threshold && result.best.confidence >= FALLBACK_THRESHOLD) {
|
|
213
|
+
const noDepthResult = findBestFuzzyMatchCore(contentLines, targetLines, offsets, threshold, false);
|
|
214
|
+
if (noDepthResult.best && noDepthResult.best.confidence > result.best.confidence) {
|
|
215
|
+
result = noDepthResult;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
199
222
|
export function findEditMatch(
|
|
200
223
|
content: string,
|
|
201
224
|
target: string,
|
|
@@ -292,8 +292,8 @@ export async function fetchMCPToolSchema(
|
|
|
292
292
|
mcpSchemaCache.set(cacheKey, tool);
|
|
293
293
|
return tool;
|
|
294
294
|
}
|
|
295
|
-
} catch {
|
|
296
|
-
|
|
295
|
+
} catch (error) {
|
|
296
|
+
logger.warn("Failed to fetch MCP tool schema", { mcpToolName, isWebsetsTool, error: String(error) });
|
|
297
297
|
}
|
|
298
298
|
return null;
|
|
299
299
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Agents are embedded at build time via Bun's import with { type: "text" }.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { parseAgentFields, parseFrontmatter } from "../../../discovery/helpers";
|
|
7
8
|
import exploreMd from "../../../prompts/agents/explore.md" with { type: "text" };
|
|
8
9
|
// Embed agent markdown files at build time
|
|
9
10
|
import agentFrontmatterTemplate from "../../../prompts/agents/frontmatter.md" with { type: "text" };
|
|
@@ -18,6 +19,7 @@ interface AgentFrontmatter {
|
|
|
18
19
|
description: string;
|
|
19
20
|
spawns?: string;
|
|
20
21
|
model?: string;
|
|
22
|
+
thinkingLevel?: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
interface EmbeddedAgentDef {
|
|
@@ -71,80 +73,19 @@ const EMBEDDED_AGENTS: { name: string; content: string }[] = EMBEDDED_AGENT_DEFS
|
|
|
71
73
|
content: buildAgentContent(def),
|
|
72
74
|
}));
|
|
73
75
|
|
|
74
|
-
/**
|
|
75
|
-
* Parse YAML frontmatter from markdown content.
|
|
76
|
-
*/
|
|
77
|
-
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
78
|
-
const frontmatter: Record<string, string> = {};
|
|
79
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
80
|
-
|
|
81
|
-
if (!normalized.startsWith("---")) {
|
|
82
|
-
return { frontmatter, body: normalized };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
86
|
-
if (endIndex === -1) {
|
|
87
|
-
return { frontmatter, body: normalized };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
91
|
-
const body = normalized.slice(endIndex + 4).trim();
|
|
92
|
-
|
|
93
|
-
for (const line of frontmatterBlock.split("\n")) {
|
|
94
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
95
|
-
if (match) {
|
|
96
|
-
let value = match[2].trim();
|
|
97
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
98
|
-
value = value.slice(1, -1);
|
|
99
|
-
}
|
|
100
|
-
frontmatter[match[1]] = value;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { frontmatter, body };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
76
|
/**
|
|
108
77
|
* Parse an agent from embedded content.
|
|
109
78
|
*/
|
|
110
79
|
function parseAgent(fileName: string, content: string, source: AgentSource): AgentDefinition | null {
|
|
111
80
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
81
|
+
const fields = parseAgentFields(frontmatter);
|
|
112
82
|
|
|
113
|
-
if (!
|
|
83
|
+
if (!fields) {
|
|
114
84
|
return null;
|
|
115
85
|
}
|
|
116
86
|
|
|
117
|
-
const tools = frontmatter.tools
|
|
118
|
-
?.split(",")
|
|
119
|
-
.map((t) => t.trim())
|
|
120
|
-
.filter(Boolean);
|
|
121
|
-
|
|
122
|
-
// Parse spawns field
|
|
123
|
-
let spawns: string[] | "*" | undefined;
|
|
124
|
-
if (frontmatter.spawns !== undefined) {
|
|
125
|
-
const spawnsRaw = frontmatter.spawns.trim();
|
|
126
|
-
if (spawnsRaw === "*") {
|
|
127
|
-
spawns = "*";
|
|
128
|
-
} else if (spawnsRaw) {
|
|
129
|
-
spawns = spawnsRaw
|
|
130
|
-
.split(",")
|
|
131
|
-
.map((s) => s.trim())
|
|
132
|
-
.filter(Boolean);
|
|
133
|
-
if (spawns.length === 0) spawns = undefined;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Backward compat: infer spawns: "*" when tools includes "task"
|
|
138
|
-
if (spawns === undefined && tools?.includes("task")) {
|
|
139
|
-
spawns = "*";
|
|
140
|
-
}
|
|
141
|
-
|
|
142
87
|
return {
|
|
143
|
-
|
|
144
|
-
description: frontmatter.description,
|
|
145
|
-
tools: tools && tools.length > 0 ? tools : undefined,
|
|
146
|
-
spawns,
|
|
147
|
-
model: frontmatter.model,
|
|
88
|
+
...fields,
|
|
148
89
|
systemPrompt: body,
|
|
149
90
|
source,
|
|
150
91
|
filePath: `embedded:${fileName}`,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { type SlashCommand, slashCommandCapability } from "../../../capability/slash-command";
|
|
9
9
|
import { loadCapability } from "../../../discovery";
|
|
10
|
+
import { parseFrontmatter } from "../../../discovery/helpers";
|
|
10
11
|
|
|
11
12
|
// Embed command markdown files at build time
|
|
12
13
|
import initMd from "../../../prompts/agents/init.md" with { type: "text" };
|
|
@@ -27,37 +28,10 @@ export interface WorkflowCommand {
|
|
|
27
28
|
filePath: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const frontmatter: Record<string, string> = {};
|
|
35
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
36
|
-
|
|
37
|
-
if (!normalized.startsWith("---")) {
|
|
38
|
-
return { frontmatter, body: normalized };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
42
|
-
if (endIndex === -1) {
|
|
43
|
-
return { frontmatter, body: normalized };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
47
|
-
const body = normalized.slice(endIndex + 4).trim();
|
|
48
|
-
|
|
49
|
-
for (const line of frontmatterBlock.split("\n")) {
|
|
50
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
51
|
-
if (match) {
|
|
52
|
-
let value = match[2].trim();
|
|
53
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
54
|
-
value = value.slice(1, -1);
|
|
55
|
-
}
|
|
56
|
-
frontmatter[match[1]] = value;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return { frontmatter, body };
|
|
31
|
+
/** Extract string value from frontmatter field */
|
|
32
|
+
function getString(frontmatter: Record<string, unknown>, key: string): string {
|
|
33
|
+
const value = frontmatter[key];
|
|
34
|
+
return typeof value === "string" ? value : "";
|
|
61
35
|
}
|
|
62
36
|
|
|
63
37
|
/** Cache for bundled commands */
|
|
@@ -79,7 +53,7 @@ export function loadBundledCommands(): WorkflowCommand[] {
|
|
|
79
53
|
|
|
80
54
|
commands.push({
|
|
81
55
|
name: cmdName,
|
|
82
|
-
description: frontmatter
|
|
56
|
+
description: getString(frontmatter, "description"),
|
|
83
57
|
instructions: body,
|
|
84
58
|
source: "bundled",
|
|
85
59
|
filePath: `embedded:${name}`,
|
|
@@ -115,7 +89,7 @@ export async function discoverCommands(cwd: string): Promise<WorkflowCommand[]>
|
|
|
115
89
|
|
|
116
90
|
commands.push({
|
|
117
91
|
name: cmd.name,
|
|
118
|
-
description: frontmatter
|
|
92
|
+
description: getString(frontmatter, "description"),
|
|
119
93
|
instructions: body,
|
|
120
94
|
source,
|
|
121
95
|
filePath: cmd.path,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { findAllNearestProjectConfigDirs, getConfigDirs } from "../../../config";
|
|
18
|
+
import { parseAgentFields, parseFrontmatter } from "../../../discovery/helpers";
|
|
18
19
|
import { loadBundledAgents } from "./agents";
|
|
19
20
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
20
21
|
|
|
@@ -24,40 +25,6 @@ export interface DiscoveryResult {
|
|
|
24
25
|
projectAgentsDir: string | null;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
* Parse YAML frontmatter from markdown content.
|
|
29
|
-
*/
|
|
30
|
-
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
31
|
-
const frontmatter: Record<string, string> = {};
|
|
32
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
33
|
-
|
|
34
|
-
if (!normalized.startsWith("---")) {
|
|
35
|
-
return { frontmatter, body: normalized };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
39
|
-
if (endIndex === -1) {
|
|
40
|
-
return { frontmatter, body: normalized };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
44
|
-
const body = normalized.slice(endIndex + 4).trim();
|
|
45
|
-
|
|
46
|
-
for (const line of frontmatterBlock.split("\n")) {
|
|
47
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
48
|
-
if (match) {
|
|
49
|
-
let value = match[2].trim();
|
|
50
|
-
// Strip quotes
|
|
51
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
52
|
-
value = value.slice(1, -1);
|
|
53
|
-
}
|
|
54
|
-
frontmatter[match[1]] = value;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return { frontmatter, body };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
28
|
/**
|
|
62
29
|
* Load agents from a directory.
|
|
63
30
|
*/
|
|
@@ -95,43 +62,14 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[]
|
|
|
95
62
|
}
|
|
96
63
|
|
|
97
64
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
65
|
+
const fields = parseAgentFields(frontmatter);
|
|
98
66
|
|
|
99
|
-
|
|
100
|
-
if (!frontmatter.name || !frontmatter.description) {
|
|
67
|
+
if (!fields) {
|
|
101
68
|
continue;
|
|
102
69
|
}
|
|
103
70
|
|
|
104
|
-
const tools = frontmatter.tools
|
|
105
|
-
?.split(",")
|
|
106
|
-
.map((t) => t.trim())
|
|
107
|
-
.filter(Boolean);
|
|
108
|
-
|
|
109
|
-
// Parse spawns field
|
|
110
|
-
let spawns: string[] | "*" | undefined;
|
|
111
|
-
if (frontmatter.spawns !== undefined) {
|
|
112
|
-
const spawnsRaw = frontmatter.spawns.trim();
|
|
113
|
-
if (spawnsRaw === "*") {
|
|
114
|
-
spawns = "*";
|
|
115
|
-
} else if (spawnsRaw) {
|
|
116
|
-
spawns = spawnsRaw
|
|
117
|
-
.split(",")
|
|
118
|
-
.map((s) => s.trim())
|
|
119
|
-
.filter(Boolean);
|
|
120
|
-
if (spawns.length === 0) spawns = undefined;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Backward compat: infer spawns: "*" when tools includes "task"
|
|
125
|
-
if (spawns === undefined && tools?.includes("task")) {
|
|
126
|
-
spawns = "*";
|
|
127
|
-
}
|
|
128
|
-
|
|
129
71
|
agents.push({
|
|
130
|
-
|
|
131
|
-
description: frontmatter.description,
|
|
132
|
-
tools: tools && tools.length > 0 ? tools : undefined,
|
|
133
|
-
spawns,
|
|
134
|
-
model: frontmatter.model,
|
|
72
|
+
...fields,
|
|
135
73
|
systemPrompt: body,
|
|
136
74
|
source,
|
|
137
75
|
filePath,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Runs each subagent in a Bun Worker and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { AuthStorage } from "../../auth-storage";
|
|
9
9
|
import type { EventBus } from "../../event-bus";
|
|
10
10
|
import { callTool } from "../../mcp/client";
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type AgentProgress,
|
|
19
19
|
MAX_OUTPUT_BYTES,
|
|
20
20
|
MAX_OUTPUT_LINES,
|
|
21
|
+
type ReviewFinding,
|
|
21
22
|
type SingleResult,
|
|
22
23
|
TASK_SUBAGENT_EVENT_CHANNEL,
|
|
23
24
|
TASK_SUBAGENT_PROGRESS_CHANNEL,
|
|
@@ -39,6 +40,7 @@ export interface ExecutorOptions {
|
|
|
39
40
|
taskId: string;
|
|
40
41
|
context?: string;
|
|
41
42
|
modelOverride?: string;
|
|
43
|
+
thinkingLevel?: ThinkingLevel;
|
|
42
44
|
outputSchema?: unknown;
|
|
43
45
|
enableLsp?: boolean;
|
|
44
46
|
signal?: AbortSignal;
|
|
@@ -183,8 +185,20 @@ function extractMCPToolMetadata(mcpManager: MCPManager): MCPToolMetadata[] {
|
|
|
183
185
|
* Run a single agent in a worker.
|
|
184
186
|
*/
|
|
185
187
|
export async function runSubprocess(options: ExecutorOptions): Promise<SingleResult> {
|
|
186
|
-
const {
|
|
187
|
-
|
|
188
|
+
const {
|
|
189
|
+
cwd,
|
|
190
|
+
agent,
|
|
191
|
+
task,
|
|
192
|
+
index,
|
|
193
|
+
taskId,
|
|
194
|
+
context,
|
|
195
|
+
modelOverride,
|
|
196
|
+
thinkingLevel,
|
|
197
|
+
outputSchema,
|
|
198
|
+
enableLsp,
|
|
199
|
+
signal,
|
|
200
|
+
onProgress,
|
|
201
|
+
} = options;
|
|
188
202
|
const startTime = Date.now();
|
|
189
203
|
|
|
190
204
|
// Initialize progress
|
|
@@ -578,6 +592,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
578
592
|
task: fullTask,
|
|
579
593
|
systemPrompt: agent.systemPrompt,
|
|
580
594
|
model: resolvedModel,
|
|
595
|
+
thinkingLevel,
|
|
581
596
|
toolNames,
|
|
582
597
|
outputSchema,
|
|
583
598
|
sessionFile,
|
|
@@ -751,6 +766,20 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
751
766
|
// Not valid JSON, keep as string
|
|
752
767
|
}
|
|
753
768
|
}
|
|
769
|
+
// Special case: merge report_finding data into review output for parent visibility
|
|
770
|
+
const reportFindings = progress.extractedToolData?.report_finding as ReviewFinding[] | undefined;
|
|
771
|
+
if (
|
|
772
|
+
Array.isArray(reportFindings) &&
|
|
773
|
+
reportFindings.length > 0 &&
|
|
774
|
+
completeData &&
|
|
775
|
+
typeof completeData === "object" &&
|
|
776
|
+
!Array.isArray(completeData)
|
|
777
|
+
) {
|
|
778
|
+
const record = completeData as Record<string, unknown>;
|
|
779
|
+
if (!("findings" in record)) {
|
|
780
|
+
completeData = { ...record, findings: reportFindings };
|
|
781
|
+
}
|
|
782
|
+
}
|
|
754
783
|
try {
|
|
755
784
|
rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
|
|
756
785
|
} catch (err) {
|
|
@@ -156,6 +156,11 @@ export async function createTaskTool(
|
|
|
156
156
|
const shouldInheritSessionModel = model === undefined && isDefaultModelAlias(agent.model);
|
|
157
157
|
const sessionModel = shouldInheritSessionModel ? session.getActiveModelString?.() : undefined;
|
|
158
158
|
const modelOverride = model ?? sessionModel ?? session.getModelString?.();
|
|
159
|
+
const thinkingLevelOverride = agent.thinkingLevel;
|
|
160
|
+
|
|
161
|
+
// Output schema priority: agent frontmatter > params > inherited from parent session
|
|
162
|
+
const schemaOverridden = outputSchema !== undefined && agent.output !== undefined;
|
|
163
|
+
const effectiveOutputSchema = agent.output ?? outputSchema ?? session.outputSchema;
|
|
159
164
|
|
|
160
165
|
// Handle empty or missing tasks
|
|
161
166
|
if (!params.tasks || params.tasks.length === 0) {
|
|
@@ -345,7 +350,8 @@ export async function createTaskTool(
|
|
|
345
350
|
taskId: task.taskId,
|
|
346
351
|
context: undefined, // Already prepended above
|
|
347
352
|
modelOverride,
|
|
348
|
-
|
|
353
|
+
thinkingLevel: thinkingLevelOverride,
|
|
354
|
+
outputSchema: effectiveOutputSchema,
|
|
349
355
|
sessionFile,
|
|
350
356
|
persistArtifacts: !!artifactsDir,
|
|
351
357
|
artifactsDir: effectiveArtifactsDir,
|
|
@@ -399,9 +405,12 @@ export async function createTaskTool(
|
|
|
399
405
|
const outputIds = results.map((r) => r.taskId);
|
|
400
406
|
const outputHint =
|
|
401
407
|
outputIds.length > 0 ? `\n\nUse output tool for full logs: output ids ${outputIds.join(", ")}` : "";
|
|
408
|
+
const schemaNote = schemaOverridden
|
|
409
|
+
? `\n\nNote: Agent '${agentName}' has a fixed output schema; your 'output' parameter was ignored.\nRequired schema: ${JSON.stringify(agent.output)}`
|
|
410
|
+
: "";
|
|
402
411
|
const summary = `${successCount}/${results.length} succeeded [${formatDuration(
|
|
403
412
|
totalDuration,
|
|
404
|
-
)}]\n\n${summaries.join("\n\n---\n\n")}${outputHint}`;
|
|
413
|
+
)}]\n\n${summaries.join("\n\n---\n\n")}${outputHint}${schemaNote}`;
|
|
405
414
|
|
|
406
415
|
// Cleanup temp directory if used
|
|
407
416
|
if (tempArtifactsDir) {
|
|
@@ -369,18 +369,28 @@ function renderAgentProgress(
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
for (const [toolName, dataArray] of Object.entries(progress.extractedToolData)) {
|
|
372
|
+
// Handle report_finding with tree formatting
|
|
373
|
+
if (toolName === "report_finding" && (dataArray as ReportFindingDetails[]).length > 0) {
|
|
374
|
+
const findings = dataArray as ReportFindingDetails[];
|
|
375
|
+
lines.push(`${continuePrefix}${formatFindingSummary(findings, theme)}`);
|
|
376
|
+
lines.push(...renderFindings(findings, continuePrefix, expanded, theme));
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
|
|
372
380
|
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
373
381
|
if (handler?.renderInline) {
|
|
374
|
-
|
|
375
|
-
const recentData = (dataArray as unknown[]).slice(-
|
|
382
|
+
const displayCount = expanded ? (dataArray as unknown[]).length : 3;
|
|
383
|
+
const recentData = (dataArray as unknown[]).slice(-displayCount);
|
|
376
384
|
for (const data of recentData) {
|
|
377
385
|
const component = handler.renderInline(data, theme);
|
|
378
386
|
if (component instanceof Text) {
|
|
379
387
|
lines.push(`${continuePrefix}${component.getText()}`);
|
|
380
388
|
}
|
|
381
389
|
}
|
|
382
|
-
if (dataArray.length >
|
|
383
|
-
lines.push(
|
|
390
|
+
if ((dataArray as unknown[]).length > displayCount) {
|
|
391
|
+
lines.push(
|
|
392
|
+
`${continuePrefix}${theme.fg("dim", formatMoreItems((dataArray as unknown[]).length - displayCount, "item", theme))}`,
|
|
393
|
+
);
|
|
384
394
|
}
|
|
385
395
|
}
|
|
386
396
|
}
|
|
@@ -436,7 +446,6 @@ function renderReviewResult(
|
|
|
436
446
|
lines.push(`${continuePrefix}${formatFindingSummary(findings, theme)}`);
|
|
437
447
|
|
|
438
448
|
if (findings.length > 0) {
|
|
439
|
-
lines.push(`${continuePrefix}`); // Spacing
|
|
440
449
|
lines.push(...renderFindings(findings, continuePrefix, expanded, theme));
|
|
441
450
|
}
|
|
442
451
|
|
|
@@ -453,11 +462,14 @@ function renderFindings(
|
|
|
453
462
|
theme: Theme,
|
|
454
463
|
): string[] {
|
|
455
464
|
const lines: string[] = [];
|
|
456
|
-
|
|
465
|
+
|
|
466
|
+
// Sort by priority (lower = more severe) when collapsed to show most important first
|
|
467
|
+
const sortedFindings = expanded ? findings : [...findings].sort((a, b) => a.priority - b.priority);
|
|
468
|
+
const displayCount = expanded ? sortedFindings.length : Math.min(3, sortedFindings.length);
|
|
457
469
|
|
|
458
470
|
for (let i = 0; i < displayCount; i++) {
|
|
459
|
-
const finding =
|
|
460
|
-
const isLastFinding = i === displayCount - 1 && (expanded ||
|
|
471
|
+
const finding = sortedFindings[i];
|
|
472
|
+
const isLastFinding = i === displayCount - 1 && (expanded || sortedFindings.length <= 3);
|
|
461
473
|
const findingPrefix = isLastFinding ? theme.tree.last : theme.tree.branch;
|
|
462
474
|
const findingContinue = isLastFinding ? " " : `${theme.tree.vertical} `;
|
|
463
475
|
|
|
@@ -538,14 +550,12 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
538
550
|
return lines;
|
|
539
551
|
}
|
|
540
552
|
if (reportFindingData && reportFindingData.length > 0) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
553
|
+
const hasCompleteData = completeData && completeData.length > 0;
|
|
554
|
+
const message = hasCompleteData
|
|
555
|
+
? "Review verdict missing expected fields"
|
|
556
|
+
: "Review incomplete (complete not called)";
|
|
557
|
+
lines.push(`${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg("dim", message)}`);
|
|
547
558
|
lines.push(`${continuePrefix}${formatFindingSummary(reportFindingData, theme)}`);
|
|
548
|
-
lines.push(`${continuePrefix}`); // Spacing
|
|
549
559
|
lines.push(...renderFindings(reportFindingData, continuePrefix, expanded, theme));
|
|
550
560
|
return lines;
|
|
551
561
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { type Static, Type } from "@sinclair/typebox";
|
|
3
4
|
|
|
@@ -106,6 +107,8 @@ export interface AgentDefinition {
|
|
|
106
107
|
tools?: string[];
|
|
107
108
|
spawns?: string[] | "*";
|
|
108
109
|
model?: string;
|
|
110
|
+
thinkingLevel?: ThinkingLevel;
|
|
111
|
+
output?: unknown;
|
|
109
112
|
source: AgentSource;
|
|
110
113
|
filePath?: string;
|
|
111
114
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { SerializedAuthStorage } from "../../auth-storage";
|
|
3
3
|
import type { SerializedModelRegistry } from "../../model-registry";
|
|
4
4
|
|
|
@@ -43,6 +43,7 @@ export interface SubagentWorkerStartPayload {
|
|
|
43
43
|
task: string;
|
|
44
44
|
systemPrompt: string;
|
|
45
45
|
model?: string;
|
|
46
|
+
thinkingLevel?: ThinkingLevel;
|
|
46
47
|
toolNames?: string[];
|
|
47
48
|
outputSchema?: unknown;
|
|
48
49
|
enableLsp?: boolean;
|
|
@@ -287,7 +287,8 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
|
|
|
287
287
|
const mcpProxyTools = payload.mcpTools?.map(createMCPProxyTool) ?? [];
|
|
288
288
|
|
|
289
289
|
// Resolve model override (equivalent to CLI's parseModelPattern with --model)
|
|
290
|
-
const { model, thinkingLevel } = resolveModelOverride(payload.model, modelRegistry);
|
|
290
|
+
const { model, thinkingLevel: modelThinkingLevel } = resolveModelOverride(payload.model, modelRegistry);
|
|
291
|
+
const thinkingLevel = modelThinkingLevel ?? payload.thinkingLevel;
|
|
291
292
|
|
|
292
293
|
// Create session manager (equivalent to CLI's --session or --no-session)
|
|
293
294
|
const sessionManager = payload.sessionFile
|
|
@@ -170,7 +170,7 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
return { content, contentType, finalUrl, ok: true, status: response.status };
|
|
173
|
-
} catch
|
|
173
|
+
} catch {
|
|
174
174
|
if (signal?.aborted) {
|
|
175
175
|
return { content: "", contentType: "", finalUrl: url, ok: false };
|
|
176
176
|
}
|
|
@@ -14,6 +14,7 @@ import { buildBetaHeader, claudeCodeHeaders, claudeCodeVersion } from "@oh-my-pi
|
|
|
14
14
|
import { getAgentDbPath, getConfigDirPaths } from "../../../config";
|
|
15
15
|
import { AgentStorage } from "../../agent-storage";
|
|
16
16
|
import type { AuthCredential, AuthCredentialEntry, AuthStorageData } from "../../auth-storage";
|
|
17
|
+
import { logger } from "../../logger";
|
|
17
18
|
import { migrateJsonStorage } from "../../storage-migration";
|
|
18
19
|
import type { AnthropicAuthConfig, AnthropicOAuthCredential, ModelsJson } from "./types";
|
|
19
20
|
|
|
@@ -48,8 +49,8 @@ async function parseEnvFile(filePath: string): Promise<Record<string, string>> {
|
|
|
48
49
|
|
|
49
50
|
result[key] = value;
|
|
50
51
|
}
|
|
51
|
-
} catch {
|
|
52
|
-
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.warn("Failed to read .env file", { path: filePath, error: String(error) });
|
|
53
54
|
}
|
|
54
55
|
return result;
|
|
55
56
|
}
|
|
@@ -82,7 +83,8 @@ async function readJson<T>(filePath: string): Promise<T | null> {
|
|
|
82
83
|
if (!(await file.exists())) return null;
|
|
83
84
|
const content = await file.text();
|
|
84
85
|
return JSON.parse(content) as T;
|
|
85
|
-
} catch {
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.warn("Failed to parse JSON file", { path: filePath, error: String(error) });
|
|
86
88
|
return null;
|
|
87
89
|
}
|
|
88
90
|
}
|