@oh-my-pi/pi-coding-agent 12.16.0 → 12.17.0
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 +11 -0
- package/package.json +7 -7
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +3 -2
- package/src/lsp/render.ts +1 -1
- package/src/mcp/tool-bridge.ts +2 -2
- package/src/modes/components/tool-execution.ts +2 -2
- package/src/patch/shared.ts +1 -2
- package/src/system-prompt.ts +192 -62
- package/src/task/render.ts +1 -1
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/calculator.ts +1 -1
- package/src/tools/fetch.ts +1 -0
- package/src/tools/find.ts +1 -1
- package/src/tools/grep.ts +1 -1
- package/src/tools/notebook.ts +1 -1
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/tools/renderers.ts +1 -5
- package/src/tools/review.ts +1 -1
- package/src/tools/ssh.ts +1 -1
- package/src/tools/todo-write.ts +1 -1
- package/src/tools/write.ts +1 -2
- package/src/web/search/index.ts +7 -7
- package/src/web/search/render.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [12.17.0] - 2026-02-21
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added timeout protection (5 seconds) for system prompt preparation with graceful fallback to minimal context on timeout
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Replaced glob-based AGENTS.md discovery with depth-limited directory traversal (depth 1-4) for improved performance and control
|
|
13
|
+
- Refactored system prompt preparation to parallelize file loading operations with a 5-second timeout to prevent startup hangs
|
|
14
|
+
- Unified `renderCall` signatures to `(args, options, theme)` across all tool renderers and extension types
|
|
15
|
+
|
|
5
16
|
## [12.16.0] - 2026-02-21
|
|
6
17
|
|
|
7
18
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.17.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -85,12 +85,12 @@
|
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@mozilla/readability": "0.6.0",
|
|
88
|
-
"@oh-my-pi/omp-stats": "12.
|
|
89
|
-
"@oh-my-pi/pi-agent-core": "12.
|
|
90
|
-
"@oh-my-pi/pi-ai": "12.
|
|
91
|
-
"@oh-my-pi/pi-natives": "12.
|
|
92
|
-
"@oh-my-pi/pi-tui": "12.
|
|
93
|
-
"@oh-my-pi/pi-utils": "12.
|
|
88
|
+
"@oh-my-pi/omp-stats": "12.17.0",
|
|
89
|
+
"@oh-my-pi/pi-agent-core": "12.17.0",
|
|
90
|
+
"@oh-my-pi/pi-ai": "12.17.0",
|
|
91
|
+
"@oh-my-pi/pi-natives": "12.17.0",
|
|
92
|
+
"@oh-my-pi/pi-tui": "12.17.0",
|
|
93
|
+
"@oh-my-pi/pi-utils": "12.17.0",
|
|
94
94
|
"@sinclair/typebox": "^0.34.48",
|
|
95
95
|
"@xterm/headless": "^6.0.0",
|
|
96
96
|
"ajv": "^8.18.0",
|
|
@@ -182,7 +182,7 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
|
|
|
182
182
|
/** Called on session lifecycle events - use to reconstruct state or cleanup resources */
|
|
183
183
|
onSession?: (event: CustomToolSessionEvent, ctx: CustomToolContext) => void | Promise<void>;
|
|
184
184
|
/** Custom rendering for tool call display - return a Component */
|
|
185
|
-
renderCall?: (args: Static<TParams>, theme: Theme) => Component;
|
|
185
|
+
renderCall?: (args: Static<TParams>, options: RenderResultOptions, theme: Theme) => Component;
|
|
186
186
|
|
|
187
187
|
/** Custom rendering for tool result display - return a Component */
|
|
188
188
|
renderResult?: (
|
|
@@ -304,7 +304,7 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
|
|
|
304
304
|
onSession?: (event: ToolSessionEvent, ctx: ExtensionContext) => void | Promise<void>;
|
|
305
305
|
|
|
306
306
|
/** Custom rendering for tool call display */
|
|
307
|
-
renderCall?: (args: Static<TParams>, theme: Theme) => Component;
|
|
307
|
+
renderCall?: (args: Static<TParams>, options: ToolRenderResultOptions, theme: Theme) => Component;
|
|
308
308
|
|
|
309
309
|
/** Custom rendering for tool result display */
|
|
310
310
|
renderResult?: (
|
|
@@ -18,7 +18,7 @@ export class RegisteredToolAdapter implements AgentTool<any, any, any> {
|
|
|
18
18
|
declare parameters: any;
|
|
19
19
|
declare label: string;
|
|
20
20
|
|
|
21
|
-
renderCall?: (args: any, theme: any) => any;
|
|
21
|
+
renderCall?: (args: any, options: any, theme: any) => any;
|
|
22
22
|
renderResult?: (result: any, options: any, theme: any, args?: any) => any;
|
|
23
23
|
|
|
24
24
|
constructor(
|
|
@@ -32,7 +32,8 @@ export class RegisteredToolAdapter implements AgentTool<any, any, any> {
|
|
|
32
32
|
// enters the custom-renderer path, gets undefined back, and silently
|
|
33
33
|
// discards tool result text (extensions without renderers show blank).
|
|
34
34
|
if (registeredTool.definition.renderCall) {
|
|
35
|
-
this.renderCall = (args: any,
|
|
35
|
+
this.renderCall = (args: any, options: any, theme: any) =>
|
|
36
|
+
registeredTool.definition.renderCall!(args, options, theme as Theme);
|
|
36
37
|
}
|
|
37
38
|
if (registeredTool.definition.renderResult) {
|
|
38
39
|
this.renderResult = (result: any, options: any, theme: any, args?: any) =>
|
package/src/lsp/render.ts
CHANGED
|
@@ -31,7 +31,7 @@ import type { LspParams, LspToolDetails } from "./types";
|
|
|
31
31
|
* Render the LSP tool call in the TUI.
|
|
32
32
|
* Shows: "lsp <operation> <file/filecount>"
|
|
33
33
|
*/
|
|
34
|
-
export function renderCall(args: LspParams, theme: Theme): Text {
|
|
34
|
+
export function renderCall(args: LspParams, _options: RenderResultOptions, theme: Theme): Text {
|
|
35
35
|
const actionLabel = (args.action ?? "request").replace(/_/g, " ");
|
|
36
36
|
const queryPreview = args.query ? truncateToWidth(args.query, TRUNCATE_LENGTHS.SHORT) : undefined;
|
|
37
37
|
|
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -198,7 +198,7 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
198
198
|
this.mcpServerName = connection.name;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
renderCall(args: unknown, theme: Theme) {
|
|
201
|
+
renderCall(args: unknown, _options: RenderResultOptions, theme: Theme) {
|
|
202
202
|
return renderMCPCall((args ?? {}) as Record<string, unknown>, theme, this.label);
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -304,7 +304,7 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
304
304
|
this.#fallbackProviderName = source?.providerName;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
renderCall(args: unknown, theme: Theme) {
|
|
307
|
+
renderCall(args: unknown, _options: RenderResultOptions, theme: Theme) {
|
|
308
308
|
return renderMCPCall((args ?? {}) as Record<string, unknown>, theme, this.label);
|
|
309
309
|
}
|
|
310
310
|
|
|
@@ -391,7 +391,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
391
391
|
const shouldRenderCall = !this.#result || !mergeCallAndResult;
|
|
392
392
|
if (shouldRenderCall && tool.renderCall) {
|
|
393
393
|
try {
|
|
394
|
-
const callComponent = tool.renderCall(this.#getCallArgsForRender(), theme);
|
|
394
|
+
const callComponent = tool.renderCall(this.#getCallArgsForRender(), this.#renderState, theme);
|
|
395
395
|
if (callComponent) {
|
|
396
396
|
this.#contentBox.addChild(ensureInvalidate(callComponent));
|
|
397
397
|
}
|
|
@@ -453,7 +453,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
453
453
|
if (shouldRenderCall) {
|
|
454
454
|
// Render call component
|
|
455
455
|
try {
|
|
456
|
-
const callComponent = renderer.renderCall(this.#getCallArgsForRender(),
|
|
456
|
+
const callComponent = renderer.renderCall(this.#getCallArgsForRender(), this.#renderState, theme);
|
|
457
457
|
if (callComponent) {
|
|
458
458
|
this.#contentBox.addChild(ensureInvalidate(callComponent));
|
|
459
459
|
}
|
package/src/patch/shared.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
ToolUIKit,
|
|
20
20
|
truncateDiffByHunk,
|
|
21
21
|
} from "../tools/render-utils";
|
|
22
|
-
import type { RenderCallOptions } from "../tools/renderers";
|
|
23
22
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
24
23
|
import type { DiffError, DiffResult, Operation } from "./types";
|
|
25
24
|
|
|
@@ -254,7 +253,7 @@ function renderDiffSection(
|
|
|
254
253
|
export const editToolRenderer = {
|
|
255
254
|
mergeCallAndResult: true,
|
|
256
255
|
|
|
257
|
-
renderCall(args: EditRenderArgs,
|
|
256
|
+
renderCall(args: EditRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
258
257
|
const ui = new ToolUIKit(uiTheme);
|
|
259
258
|
const rawPath = args.file_path || args.path || "";
|
|
260
259
|
const filePath = shortenPath(rawPath);
|
package/src/system-prompt.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System prompt construction and project context loading
|
|
3
3
|
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
4
6
|
import * as os from "node:os";
|
|
5
|
-
import
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { $env, hasFsCode, isEnoent, logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
6
9
|
import { getGpuCachePath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
7
10
|
import { $ } from "bun";
|
|
8
11
|
import { contextFileCapability } from "./capability/context-file";
|
|
@@ -49,47 +52,38 @@ async function loadPreloadedSkillContents(preloadedSkills: Skill[]): Promise<Pre
|
|
|
49
52
|
* Returns structured git data or null if not in a git repo.
|
|
50
53
|
*/
|
|
51
54
|
export async function loadGitContext(cwd: string): Promise<GitContext | null> {
|
|
52
|
-
const
|
|
55
|
+
const timeout = 3000;
|
|
56
|
+
const abortSignal = AbortSignal.timeout(timeout);
|
|
57
|
+
|
|
58
|
+
const git = async (...args: string[]): Promise<string | null> => {
|
|
53
59
|
const proc = Bun.spawn(["git", ...args], {
|
|
54
60
|
cwd,
|
|
55
61
|
stdout: "pipe",
|
|
56
62
|
stderr: "ignore",
|
|
63
|
+
timeout: timeout,
|
|
64
|
+
});
|
|
65
|
+
return untilAborted(abortSignal, async () => {
|
|
66
|
+
const exitCode = await proc.exited;
|
|
67
|
+
const stdout = await proc.stdout.text();
|
|
68
|
+
return exitCode === 0 ? stdout.trim() : null;
|
|
57
69
|
});
|
|
58
|
-
const stdoutPromise = proc.stdout ? new Response(proc.stdout).text() : Promise.resolve("");
|
|
59
|
-
const race = await Promise.race([
|
|
60
|
-
proc.exited.then(() => "exited" as const),
|
|
61
|
-
Bun.sleep(timeoutMs).then(() => "timeout" as const),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
if (race === "timeout") {
|
|
65
|
-
proc.kill();
|
|
66
|
-
await stdoutPromise.catch(() => null);
|
|
67
|
-
logger.debug("Git context command timed out", { cwd, args, timeoutMs });
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const exitCode = await proc.exited;
|
|
72
|
-
const stdout = await stdoutPromise.catch(() => "");
|
|
73
|
-
if (exitCode !== 0) return null;
|
|
74
|
-
|
|
75
|
-
const trimmed = stdout.trim();
|
|
76
|
-
return trimmed.length > 0 ? trimmed : "";
|
|
77
70
|
};
|
|
71
|
+
|
|
78
72
|
// Check if inside a git repo
|
|
79
|
-
const isGitRepo = await
|
|
73
|
+
const isGitRepo = await git("rev-parse", "--is-inside-work-tree");
|
|
80
74
|
if (isGitRepo !== "true") return null;
|
|
81
|
-
const currentBranch = await
|
|
75
|
+
const currentBranch = await git("rev-parse", "--abbrev-ref", "HEAD");
|
|
82
76
|
if (!currentBranch) return null;
|
|
83
77
|
let mainBranch = "main";
|
|
84
|
-
const mainExists = await
|
|
78
|
+
const mainExists = await git("rev-parse", "--verify", "main");
|
|
85
79
|
if (mainExists === null) {
|
|
86
|
-
const masterExists = await
|
|
80
|
+
const masterExists = await git("rev-parse", "--verify", "master");
|
|
87
81
|
if (masterExists !== null) mainBranch = "master";
|
|
88
82
|
}
|
|
89
83
|
|
|
90
84
|
const [status, commits] = await Promise.all([
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
git("status", "--porcelain", "--untracked-files=no"),
|
|
86
|
+
git("log", "--oneline", "-5"),
|
|
93
87
|
]);
|
|
94
88
|
return {
|
|
95
89
|
isRepo: true,
|
|
@@ -117,8 +111,11 @@ function parseWmicTable(output: string, header: string): string | null {
|
|
|
117
111
|
return filtered[0] ?? null;
|
|
118
112
|
}
|
|
119
113
|
|
|
120
|
-
const
|
|
114
|
+
const AGENTS_MD_MIN_DEPTH = 1;
|
|
115
|
+
const AGENTS_MD_MAX_DEPTH = 4;
|
|
121
116
|
const AGENTS_MD_LIMIT = 200;
|
|
117
|
+
const SYSTEM_PROMPT_PREP_TIMEOUT_MS = 5000;
|
|
118
|
+
const AGENTS_MD_EXCLUDED_DIRS = new Set(["node_modules", ".git"]);
|
|
122
119
|
|
|
123
120
|
interface AgentsMdSearch {
|
|
124
121
|
scopePath: string;
|
|
@@ -131,27 +128,75 @@ function normalizePath(value: string): string {
|
|
|
131
128
|
return value.replace(/\\/g, "/");
|
|
132
129
|
}
|
|
133
130
|
|
|
134
|
-
function
|
|
131
|
+
function shouldSkipAgentsDir(name: string): boolean {
|
|
132
|
+
if (AGENTS_MD_EXCLUDED_DIRS.has(name)) return true;
|
|
133
|
+
return name.startsWith(".");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function collectAgentsMdFiles(
|
|
137
|
+
root: string,
|
|
138
|
+
dir: string,
|
|
139
|
+
depth: number,
|
|
140
|
+
limit: number,
|
|
141
|
+
discovered: Set<string>,
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
if (depth > AGENTS_MD_MAX_DEPTH || discovered.size >= limit) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let entries: fs.Dirent[];
|
|
135
148
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
150
|
+
} catch {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (depth >= AGENTS_MD_MIN_DEPTH) {
|
|
155
|
+
const hasAgentsMd = entries.some(entry => entry.isFile() && entry.name === "AGENTS.md");
|
|
156
|
+
if (hasAgentsMd) {
|
|
157
|
+
const relPath = normalizePath(path.relative(root, path.join(dir, "AGENTS.md")));
|
|
158
|
+
if (relPath.length > 0) {
|
|
159
|
+
discovered.add(relPath);
|
|
160
|
+
}
|
|
161
|
+
if (discovered.size >= limit) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (depth === AGENTS_MD_MAX_DEPTH) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const childDirs = entries
|
|
172
|
+
.filter(entry => entry.isDirectory() && !shouldSkipAgentsDir(entry.name))
|
|
173
|
+
.map(entry => entry.name)
|
|
174
|
+
.sort();
|
|
175
|
+
|
|
176
|
+
await Promise.all(
|
|
177
|
+
childDirs.map(async child => {
|
|
178
|
+
if (discovered.size >= limit) return;
|
|
179
|
+
await collectAgentsMdFiles(root, path.join(dir, child), depth + 1, limit, discovered);
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function listAgentsMdFiles(root: string, limit: number): Promise<string[]> {
|
|
185
|
+
try {
|
|
186
|
+
const discovered = new Set<string>();
|
|
187
|
+
await collectAgentsMdFiles(root, root, 0, limit, discovered);
|
|
188
|
+
return Array.from(discovered).sort().slice(0, limit);
|
|
144
189
|
} catch {
|
|
145
190
|
return [];
|
|
146
191
|
}
|
|
147
192
|
}
|
|
148
193
|
|
|
149
|
-
function buildAgentsMdSearch(cwd: string): AgentsMdSearch {
|
|
150
|
-
const files = listAgentsMdFiles(cwd, AGENTS_MD_LIMIT);
|
|
194
|
+
async function buildAgentsMdSearch(cwd: string): Promise<AgentsMdSearch> {
|
|
195
|
+
const files = await listAgentsMdFiles(cwd, AGENTS_MD_LIMIT);
|
|
151
196
|
return {
|
|
152
197
|
scopePath: ".",
|
|
153
198
|
limit: AGENTS_MD_LIMIT,
|
|
154
|
-
pattern:
|
|
199
|
+
pattern: `AGENTS.md depth ${AGENTS_MD_MIN_DEPTH}-${AGENTS_MD_MAX_DEPTH}`,
|
|
155
200
|
files,
|
|
156
201
|
};
|
|
157
202
|
}
|
|
@@ -463,12 +508,114 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
463
508
|
rules,
|
|
464
509
|
} = options;
|
|
465
510
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
466
|
-
const
|
|
467
|
-
|
|
511
|
+
const preloadedSkills = providedPreloadedSkills;
|
|
512
|
+
|
|
513
|
+
const prepPromise = (async () => {
|
|
514
|
+
const systemPromptCustomizationPromise = (async () => {
|
|
515
|
+
const customization = await loadSystemPromptFiles({ cwd: resolvedCwd });
|
|
516
|
+
debugStartup("system-prompt:loadSystemPromptFiles:done");
|
|
517
|
+
return customization;
|
|
518
|
+
})();
|
|
519
|
+
const contextFilesPromise = providedContextFiles
|
|
520
|
+
? Promise.resolve(providedContextFiles)
|
|
521
|
+
: loadProjectContextFiles({ cwd: resolvedCwd });
|
|
522
|
+
const agentsMdSearchPromise = buildAgentsMdSearch(resolvedCwd);
|
|
523
|
+
const skillsPromise: Promise<Skill[]> =
|
|
524
|
+
providedSkills !== undefined
|
|
525
|
+
? Promise.resolve(providedSkills)
|
|
526
|
+
: skillsSettings?.enabled !== false
|
|
527
|
+
? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
|
|
528
|
+
: Promise.resolve([]);
|
|
529
|
+
const preloadedSkillContentsPromise = (async () => {
|
|
530
|
+
debugStartup("system-prompt:loadPreloadedSkills:start");
|
|
531
|
+
const loaded = preloadedSkills ? await loadPreloadedSkillContents(preloadedSkills) : [];
|
|
532
|
+
debugStartup("system-prompt:loadPreloadedSkills:done");
|
|
533
|
+
return loaded;
|
|
534
|
+
})();
|
|
535
|
+
const gitPromise = (async () => {
|
|
536
|
+
debugStartup("system-prompt:loadGitContext:start");
|
|
537
|
+
const loaded = await loadGitContext(resolvedCwd);
|
|
538
|
+
debugStartup("system-prompt:loadGitContext:done");
|
|
539
|
+
return loaded;
|
|
540
|
+
})();
|
|
541
|
+
|
|
542
|
+
const [
|
|
543
|
+
resolvedCustomPrompt,
|
|
544
|
+
resolvedAppendPrompt,
|
|
545
|
+
systemPromptCustomization,
|
|
546
|
+
contextFiles,
|
|
547
|
+
agentsMdSearch,
|
|
548
|
+
skills,
|
|
549
|
+
preloadedSkillContents,
|
|
550
|
+
git,
|
|
551
|
+
] = await Promise.all([
|
|
552
|
+
resolvePromptInput(customPrompt, "system prompt"),
|
|
553
|
+
resolvePromptInput(appendSystemPrompt, "append system prompt"),
|
|
554
|
+
systemPromptCustomizationPromise,
|
|
555
|
+
contextFilesPromise,
|
|
556
|
+
agentsMdSearchPromise,
|
|
557
|
+
skillsPromise,
|
|
558
|
+
preloadedSkillContentsPromise,
|
|
559
|
+
gitPromise,
|
|
560
|
+
]);
|
|
468
561
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
562
|
+
return {
|
|
563
|
+
resolvedCustomPrompt,
|
|
564
|
+
resolvedAppendPrompt,
|
|
565
|
+
systemPromptCustomization,
|
|
566
|
+
contextFiles,
|
|
567
|
+
agentsMdSearch,
|
|
568
|
+
skills,
|
|
569
|
+
preloadedSkillContents,
|
|
570
|
+
git,
|
|
571
|
+
};
|
|
572
|
+
})();
|
|
573
|
+
|
|
574
|
+
const prepResult = await Promise.race([
|
|
575
|
+
prepPromise
|
|
576
|
+
.then(value => ({ type: "ready" as const, value }))
|
|
577
|
+
.catch(error => ({ type: "error" as const, error })),
|
|
578
|
+
Bun.sleep(SYSTEM_PROMPT_PREP_TIMEOUT_MS).then(() => ({ type: "timeout" as const })),
|
|
579
|
+
]);
|
|
580
|
+
|
|
581
|
+
let resolvedCustomPrompt: string | undefined;
|
|
582
|
+
let resolvedAppendPrompt: string | undefined;
|
|
583
|
+
let systemPromptCustomization: string | null = null;
|
|
584
|
+
let contextFiles: Array<{ path: string; content: string; depth?: number }> = providedContextFiles ?? [];
|
|
585
|
+
let agentsMdSearch: AgentsMdSearch = {
|
|
586
|
+
scopePath: ".",
|
|
587
|
+
limit: AGENTS_MD_LIMIT,
|
|
588
|
+
pattern: `AGENTS.md depth ${AGENTS_MD_MIN_DEPTH}-${AGENTS_MD_MAX_DEPTH}`,
|
|
589
|
+
files: [],
|
|
590
|
+
};
|
|
591
|
+
let skills: Skill[] = providedSkills ?? [];
|
|
592
|
+
let preloadedSkillContents: PreloadedSkill[] = [];
|
|
593
|
+
let git: GitContext | null = null;
|
|
594
|
+
|
|
595
|
+
if (prepResult.type === "timeout") {
|
|
596
|
+
logger.warn("System prompt preparation timed out; using minimal startup context", {
|
|
597
|
+
cwd: resolvedCwd,
|
|
598
|
+
timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS,
|
|
599
|
+
});
|
|
600
|
+
process.stderr.write(
|
|
601
|
+
`Warning: system prompt preparation timed out after ${SYSTEM_PROMPT_PREP_TIMEOUT_MS}ms; using minimal startup context.\n`,
|
|
602
|
+
);
|
|
603
|
+
} else if (prepResult.type === "error") {
|
|
604
|
+
logger.warn("System prompt preparation failed; using minimal startup context", {
|
|
605
|
+
cwd: resolvedCwd,
|
|
606
|
+
error: String(prepResult.error),
|
|
607
|
+
});
|
|
608
|
+
process.stderr.write("Warning: system prompt preparation failed; using minimal startup context.\n");
|
|
609
|
+
} else {
|
|
610
|
+
resolvedCustomPrompt = prepResult.value.resolvedCustomPrompt;
|
|
611
|
+
resolvedAppendPrompt = prepResult.value.resolvedAppendPrompt;
|
|
612
|
+
systemPromptCustomization = prepResult.value.systemPromptCustomization;
|
|
613
|
+
contextFiles = prepResult.value.contextFiles;
|
|
614
|
+
agentsMdSearch = prepResult.value.agentsMdSearch;
|
|
615
|
+
skills = prepResult.value.skills;
|
|
616
|
+
preloadedSkillContents = prepResult.value.preloadedSkillContents;
|
|
617
|
+
git = prepResult.value.git;
|
|
618
|
+
}
|
|
472
619
|
|
|
473
620
|
const now = new Date();
|
|
474
621
|
const date = now.toLocaleDateString("en-CA", {
|
|
@@ -487,10 +634,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
487
634
|
timeZoneName: "short",
|
|
488
635
|
});
|
|
489
636
|
|
|
490
|
-
// Resolve context files: use provided or discover
|
|
491
|
-
const contextFiles = providedContextFiles ?? (await loadProjectContextFiles({ cwd: resolvedCwd }));
|
|
492
|
-
const agentsMdSearch = buildAgentsMdSearch(resolvedCwd);
|
|
493
|
-
|
|
494
637
|
// Build tool descriptions array
|
|
495
638
|
// Priority: toolNames (explicit list) > tools (Map) > defaults
|
|
496
639
|
// Default includes both bash and python; actual availability determined by settings in createTools
|
|
@@ -512,19 +655,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
512
655
|
name,
|
|
513
656
|
description: tools?.get(name)?.description ?? "",
|
|
514
657
|
}));
|
|
515
|
-
// Resolve skills: use provided or discover
|
|
516
|
-
const skills =
|
|
517
|
-
providedSkills ??
|
|
518
|
-
(skillsSettings?.enabled !== false ? (await loadSkills({ ...skillsSettings, cwd: resolvedCwd })).skills : []);
|
|
519
|
-
const preloadedSkills = providedPreloadedSkills;
|
|
520
|
-
debugStartup("system-prompt:loadPreloadedSkills:start");
|
|
521
|
-
const preloadedSkillContents = preloadedSkills ? await loadPreloadedSkillContents(preloadedSkills) : [];
|
|
522
|
-
debugStartup("system-prompt:loadPreloadedSkills:done");
|
|
523
|
-
|
|
524
|
-
// Get git context
|
|
525
|
-
debugStartup("system-prompt:loadGitContext:start");
|
|
526
|
-
const git = await loadGitContext(resolvedCwd);
|
|
527
|
-
debugStartup("system-prompt:loadGitContext:done");
|
|
528
658
|
|
|
529
659
|
// Filter skills to only include those with read tool
|
|
530
660
|
const hasRead = tools?.has("read");
|
package/src/task/render.ts
CHANGED
|
@@ -445,7 +445,7 @@ function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string
|
|
|
445
445
|
/**
|
|
446
446
|
* Render the tool call arguments.
|
|
447
447
|
*/
|
|
448
|
-
export function renderCall(args: TaskParams, theme: Theme): Component {
|
|
448
|
+
export function renderCall(args: TaskParams, _options: RenderResultOptions, theme: Theme): Component {
|
|
449
449
|
const lines: string[] = [];
|
|
450
450
|
lines.push(renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme));
|
|
451
451
|
|
package/src/tools/ask.ts
CHANGED
|
@@ -380,7 +380,7 @@ interface AskRenderArgs {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
export const askToolRenderer = {
|
|
383
|
-
renderCall(args: AskRenderArgs, uiTheme: Theme): Component {
|
|
383
|
+
renderCall(args: AskRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
384
384
|
const ui = new ToolUIKit(uiTheme);
|
|
385
385
|
const label = ui.title("Ask");
|
|
386
386
|
|
package/src/tools/bash.ts
CHANGED
|
@@ -237,7 +237,7 @@ function formatBashCommand(args: BashRenderArgs, _uiTheme: Theme): string {
|
|
|
237
237
|
export const BASH_PREVIEW_LINES = 10;
|
|
238
238
|
|
|
239
239
|
export const bashToolRenderer = {
|
|
240
|
-
renderCall(args: BashRenderArgs, uiTheme: Theme): Component {
|
|
240
|
+
renderCall(args: BashRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
241
241
|
const cmdText = formatBashCommand(args, uiTheme);
|
|
242
242
|
const text = renderStatusLine({ icon: "pending", title: "Bash", description: cmdText }, uiTheme);
|
|
243
243
|
return new Text(text, 0, 0);
|
package/src/tools/calculator.ts
CHANGED
|
@@ -444,7 +444,7 @@ export const calculatorToolRenderer = {
|
|
|
444
444
|
* Render the tool call header showing the first expression and count.
|
|
445
445
|
* Format: "Calc <expression> (N calcs)"
|
|
446
446
|
*/
|
|
447
|
-
renderCall(args: CalculatorRenderArgs, uiTheme: Theme): Component {
|
|
447
|
+
renderCall(args: CalculatorRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
448
448
|
const count = args.calculations?.length ?? 0;
|
|
449
449
|
const firstExpression = args.calculations?.[0]?.expression;
|
|
450
450
|
const description = firstExpression ? truncateToWidth(firstExpression, TRUNCATE_LENGTHS.TITLE) : undefined;
|
package/src/tools/fetch.ts
CHANGED
|
@@ -967,6 +967,7 @@ function countNonEmptyLines(text: string): number {
|
|
|
967
967
|
/** Render fetch call (URL preview) */
|
|
968
968
|
export function renderFetchCall(
|
|
969
969
|
args: { url?: string; timeout?: number; raw?: boolean },
|
|
970
|
+
_options: RenderResultOptions,
|
|
970
971
|
uiTheme: Theme = theme,
|
|
971
972
|
): Component {
|
|
972
973
|
const url = args.url ?? "";
|
package/src/tools/find.ts
CHANGED
|
@@ -407,7 +407,7 @@ const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
|
407
407
|
|
|
408
408
|
export const findToolRenderer = {
|
|
409
409
|
inline: true,
|
|
410
|
-
renderCall(args: FindRenderArgs, uiTheme: Theme): Component {
|
|
410
|
+
renderCall(args: FindRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
411
411
|
const meta: string[] = [];
|
|
412
412
|
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
413
413
|
|
package/src/tools/grep.ts
CHANGED
|
@@ -309,7 +309,7 @@ const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
|
309
309
|
|
|
310
310
|
export const grepToolRenderer = {
|
|
311
311
|
inline: true,
|
|
312
|
-
renderCall(args: GrepRenderArgs, uiTheme: Theme): Component {
|
|
312
|
+
renderCall(args: GrepRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
313
313
|
const meta: string[] = [];
|
|
314
314
|
if (args.path) meta.push(`in ${args.path}`);
|
|
315
315
|
if (args.glob) meta.push(`glob:${args.glob}`);
|
package/src/tools/notebook.ts
CHANGED
|
@@ -203,7 +203,7 @@ interface NotebookRenderArgs {
|
|
|
203
203
|
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
204
204
|
|
|
205
205
|
export const notebookToolRenderer = {
|
|
206
|
-
renderCall(args: NotebookRenderArgs, uiTheme: Theme): Component {
|
|
206
|
+
renderCall(args: NotebookRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
207
207
|
const meta: string[] = [];
|
|
208
208
|
const notebookPath = args.notebookPath ?? args.notebook_path;
|
|
209
209
|
const cellNumber = args.cellNumber ?? args.cell_index;
|
package/src/tools/python.ts
CHANGED
|
@@ -834,7 +834,7 @@ function formatCellOutputLines(
|
|
|
834
834
|
}
|
|
835
835
|
|
|
836
836
|
export const pythonToolRenderer = {
|
|
837
|
-
renderCall(args: PythonRenderArgs, uiTheme: Theme): Component {
|
|
837
|
+
renderCall(args: PythonRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
838
838
|
const ui = new ToolUIKit(uiTheme);
|
|
839
839
|
const cells = args.cells ?? [];
|
|
840
840
|
const cwd = getProjectDir();
|
package/src/tools/read.ts
CHANGED
|
@@ -1077,7 +1077,7 @@ interface ReadRenderArgs {
|
|
|
1077
1077
|
}
|
|
1078
1078
|
|
|
1079
1079
|
export const readToolRenderer = {
|
|
1080
|
-
renderCall(args: ReadRenderArgs, uiTheme: Theme): Component {
|
|
1080
|
+
renderCall(args: ReadRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1081
1081
|
const rawPath = args.file_path || args.path || "";
|
|
1082
1082
|
const filePath = shortenPath(rawPath);
|
|
1083
1083
|
const offset = args.offset;
|
package/src/tools/renderers.ts
CHANGED
|
@@ -23,12 +23,8 @@ import { sshToolRenderer } from "./ssh";
|
|
|
23
23
|
import { todoWriteToolRenderer } from "./todo-write";
|
|
24
24
|
import { writeToolRenderer } from "./write";
|
|
25
25
|
|
|
26
|
-
export interface RenderCallOptions {
|
|
27
|
-
spinnerFrame?: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
type ToolRenderer = {
|
|
31
|
-
renderCall: (args: unknown,
|
|
27
|
+
renderCall: (args: unknown, options: RenderResultOptions, theme: Theme) => Component;
|
|
32
28
|
renderResult: (
|
|
33
29
|
result: { content: Array<{ type: string; text?: string }>; details?: unknown; isError?: boolean },
|
|
34
30
|
options: RenderResultOptions & { renderContext?: Record<string, unknown> },
|
package/src/tools/review.ts
CHANGED
|
@@ -104,7 +104,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
|
|
|
104
104
|
};
|
|
105
105
|
},
|
|
106
106
|
|
|
107
|
-
renderCall(args, theme): Component {
|
|
107
|
+
renderCall(args, _options, theme): Component {
|
|
108
108
|
const { label, icon, color } = getPriorityDisplay(args.priority, theme);
|
|
109
109
|
const titleText = String(args.title).replace(/^\[P\d\]\s*/, "");
|
|
110
110
|
return new Text(
|
package/src/tools/ssh.ts
CHANGED
|
@@ -229,7 +229,7 @@ interface SshRenderContext {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
export const sshToolRenderer = {
|
|
232
|
-
renderCall(args: SshRenderArgs, uiTheme: Theme): Component {
|
|
232
|
+
renderCall(args: SshRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
233
233
|
const host = args.host || "…";
|
|
234
234
|
const command = args.command || "…";
|
|
235
235
|
const text = renderStatusLine({ icon: "pending", title: "SSH", description: `[${host}] $ ${command}` }, uiTheme);
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -214,7 +214,7 @@ interface TodoWriteRenderArgs {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
export const todoWriteToolRenderer = {
|
|
217
|
-
renderCall(args: TodoWriteRenderArgs, uiTheme: Theme): Component {
|
|
217
|
+
renderCall(args: TodoWriteRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
218
218
|
const count = args.todos?.length ?? 0;
|
|
219
219
|
const meta = count > 0 ? [`${count} items`] : ["empty"];
|
|
220
220
|
const text = renderStatusLine({ icon: "pending", title: "Todo Write", meta }, uiTheme);
|
package/src/tools/write.ts
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
shortenPath,
|
|
29
29
|
ToolUIKit,
|
|
30
30
|
} from "./render-utils";
|
|
31
|
-
import type { RenderCallOptions } from "./renderers";
|
|
32
31
|
|
|
33
32
|
const writeSchema = Type.Object({
|
|
34
33
|
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
@@ -189,7 +188,7 @@ function renderContentPreview(content: string, expanded: boolean, uiTheme: Theme
|
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
export const writeToolRenderer = {
|
|
192
|
-
renderCall(args: WriteRenderArgs,
|
|
191
|
+
renderCall(args: WriteRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
193
192
|
const ui = new ToolUIKit(uiTheme);
|
|
194
193
|
const rawPath = args.file_path || args.path || "";
|
|
195
194
|
const filePath = shortenPath(rawPath);
|
package/src/web/search/index.ts
CHANGED
|
@@ -285,8 +285,8 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
|
|
285
285
|
return executeSearch(toolCallId, params);
|
|
286
286
|
},
|
|
287
287
|
|
|
288
|
-
renderCall(args: SearchParams, theme: Theme) {
|
|
289
|
-
return renderSearchCall(args, theme);
|
|
288
|
+
renderCall(args: SearchParams, options: RenderResultOptions, theme: Theme) {
|
|
289
|
+
return renderSearchCall(args, options, theme);
|
|
290
290
|
},
|
|
291
291
|
|
|
292
292
|
renderResult(result, options: RenderResultOptions, theme: Theme) {
|
|
@@ -405,7 +405,7 @@ Parameters:
|
|
|
405
405
|
return executeExaTool("web_search_exa", args, "web_search_deep");
|
|
406
406
|
},
|
|
407
407
|
|
|
408
|
-
renderCall(args, theme) {
|
|
408
|
+
renderCall(args, _options, theme) {
|
|
409
409
|
return renderExaCall(args as Record<string, unknown>, "Deep Search", theme);
|
|
410
410
|
},
|
|
411
411
|
|
|
@@ -436,7 +436,7 @@ Parameters:
|
|
|
436
436
|
return executeExaTool("get_code_context_exa", params as Record<string, unknown>, "web_search_code_context");
|
|
437
437
|
},
|
|
438
438
|
|
|
439
|
-
renderCall(args, theme) {
|
|
439
|
+
renderCall(args, _options, theme) {
|
|
440
440
|
return renderExaCall(args as Record<string, unknown>, "Code Search", theme);
|
|
441
441
|
},
|
|
442
442
|
|
|
@@ -464,7 +464,7 @@ Parameters:
|
|
|
464
464
|
return executeExaTool("crawling", params as Record<string, unknown>, "web_search_crawl");
|
|
465
465
|
},
|
|
466
466
|
|
|
467
|
-
renderCall(args, theme) {
|
|
467
|
+
renderCall(args, _options, theme) {
|
|
468
468
|
const url = (args as { url: string }).url;
|
|
469
469
|
return renderExaCall({ query: url }, "Crawl URL", theme);
|
|
470
470
|
},
|
|
@@ -495,7 +495,7 @@ Parameters:
|
|
|
495
495
|
return executeExaTool("linkedin_search", params as Record<string, unknown>, "web_search_linkedin");
|
|
496
496
|
},
|
|
497
497
|
|
|
498
|
-
renderCall(args, theme) {
|
|
498
|
+
renderCall(args, _options, theme) {
|
|
499
499
|
return renderExaCall(args as Record<string, unknown>, "LinkedIn Search", theme);
|
|
500
500
|
},
|
|
501
501
|
|
|
@@ -525,7 +525,7 @@ Parameters:
|
|
|
525
525
|
return executeExaTool("company_research", params as Record<string, unknown>, "web_search_company");
|
|
526
526
|
},
|
|
527
527
|
|
|
528
|
-
renderCall(args, theme) {
|
|
528
|
+
renderCall(args, _options, theme) {
|
|
529
529
|
const name = (args as { company_name: string }).company_name;
|
|
530
530
|
return renderExaCall({ query: name }, "Company Research", theme);
|
|
531
531
|
},
|
package/src/web/search/render.ts
CHANGED
|
@@ -283,6 +283,7 @@ export function renderSearchResult(
|
|
|
283
283
|
/** Render web search call (query preview) */
|
|
284
284
|
export function renderSearchCall(
|
|
285
285
|
args: { query?: string; provider?: string; [key: string]: unknown },
|
|
286
|
+
_options: RenderResultOptions,
|
|
286
287
|
theme: Theme,
|
|
287
288
|
): Component {
|
|
288
289
|
const provider = args.provider ?? "auto";
|