@oh-my-pi/pi-coding-agent 4.3.0 → 4.3.2
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 +17 -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 +6 -1
- 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/prompt-templates.ts +5 -4
- package/src/core/sdk.ts +5 -3
- package/src/core/skills.ts +5 -4
- package/src/core/tools/bash.ts +27 -11
- package/src/core/tools/exa/mcp-client.ts +2 -2
- package/src/core/tools/render-utils.ts +4 -4
- 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/modes/interactive/components/tool-execution.ts +2 -2
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/agents/reviewer.md +32 -4
- package/src/prompts/tools/task.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.3.2] - 2026-01-11
|
|
6
|
+
### Changed
|
|
7
|
+
|
|
8
|
+
- Increased default bash output preview from 5 to 10 lines when collapsed
|
|
9
|
+
- Updated expanded bash output view to show full untruncated output when available
|
|
10
|
+
|
|
11
|
+
## [4.3.1] - 2026-01-11
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Expanded system prompt with defensive reasoning guidance and assumption checks
|
|
16
|
+
- Allowed agent frontmatter to override subagent thinking level, clamped to model capabilities
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Ensured reviewer agents use structured output schemas and include reported findings in task outputs
|
|
21
|
+
|
|
5
22
|
## [4.3.0] - 2026-01-11
|
|
6
23
|
|
|
7
24
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "4.3.
|
|
43
|
-
"@oh-my-pi/pi-agent-core": "4.3.
|
|
44
|
-
"@oh-my-pi/pi-git-tool": "4.3.
|
|
45
|
-
"@oh-my-pi/pi-tui": "4.3.
|
|
42
|
+
"@oh-my-pi/pi-ai": "4.3.2",
|
|
43
|
+
"@oh-my-pi/pi-agent-core": "4.3.2",
|
|
44
|
+
"@oh-my-pi/pi-git-tool": "4.3.2",
|
|
45
|
+
"@oh-my-pi/pi-tui": "4.3.2",
|
|
46
46
|
"@openai/agents": "^0.3.7",
|
|
47
47
|
"@sinclair/typebox": "^0.34.46",
|
|
48
48
|
"ajv": "^8.17.1",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -144,8 +144,8 @@ async function updateViaBun(): Promise<void> {
|
|
|
144
144
|
try {
|
|
145
145
|
execSync(`bun update -g ${PACKAGE}`, { stdio: "inherit" });
|
|
146
146
|
console.log(chalk.green(`\n${theme.status.success} Update complete`));
|
|
147
|
-
} catch {
|
|
148
|
-
throw new Error("bun update failed");
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new Error("bun update failed", { cause: error });
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
package/src/config.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
|
|
5
4
|
// Embed package.json at build time for config
|
|
6
5
|
import packageJson from "../package.json" with { type: "json" };
|
|
6
|
+
import { logger } from "./core/logger";
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// App Config (from embedded package.json)
|
|
@@ -244,8 +244,8 @@ export function readConfigFile<T = unknown>(
|
|
|
244
244
|
content: JSON.parse(content) as T,
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
|
-
} catch {
|
|
248
|
-
|
|
247
|
+
} catch (error) {
|
|
248
|
+
logger.warn("Failed to parse config file", { path: filePath, error: String(error) });
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -275,8 +275,8 @@ export function readAllConfigFiles<T = unknown>(
|
|
|
275
275
|
content: JSON.parse(content) as T,
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
|
-
} catch {
|
|
279
|
-
|
|
278
|
+
} catch (error) {
|
|
279
|
+
logger.warn("Failed to parse config file", { path: filePath, error: String(error) });
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
package/src/core/auth-storage.ts
CHANGED
|
@@ -939,7 +939,12 @@ export class AuthStorage {
|
|
|
939
939
|
|
|
940
940
|
this.recordSessionCredential(provider, sessionId, "oauth", selection.index);
|
|
941
941
|
return result.apiKey;
|
|
942
|
-
} catch {
|
|
942
|
+
} catch (error) {
|
|
943
|
+
logger.warn("OAuth token refresh failed, removing credential", {
|
|
944
|
+
provider,
|
|
945
|
+
index: selection.index,
|
|
946
|
+
error: String(error),
|
|
947
|
+
});
|
|
943
948
|
this.removeCredentialAt(provider, selection.index);
|
|
944
949
|
if (this.getCredentialsForProvider(provider).some((credential) => credential.type === "oauth")) {
|
|
945
950
|
return this.getApiKey(provider, sessionId, options);
|
|
@@ -11,6 +11,7 @@ import * as typebox from "@sinclair/typebox";
|
|
|
11
11
|
import { getAgentDir, getConfigDirs } from "../../config";
|
|
12
12
|
import * as piCodingAgent from "../../index";
|
|
13
13
|
import { execCommand } from "../exec";
|
|
14
|
+
import { logger } from "../logger";
|
|
14
15
|
import { createReviewCommand } from "./bundled/review";
|
|
15
16
|
import { createWorktreeCommand } from "./bundled/wt";
|
|
16
17
|
import type {
|
|
@@ -110,7 +111,8 @@ export function discoverCustomCommands(options: DiscoverCustomCommandsOptions =
|
|
|
110
111
|
let entries: Dirent[];
|
|
111
112
|
try {
|
|
112
113
|
entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
113
|
-
} catch {
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.warn("Failed to read custom commands directory", { path: commandsDir, error: String(error) });
|
|
114
116
|
continue;
|
|
115
117
|
}
|
|
116
118
|
for (const entry of entries) {
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* to avoid import resolution issues with custom tools loaded from user directories.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import * as os from "node:os";
|
|
9
8
|
import * as path from "node:path";
|
|
10
9
|
import * as typebox from "@sinclair/typebox";
|
|
11
10
|
import { toolCapability } from "../../capability/tool";
|
|
12
11
|
import { type CustomTool, loadCapability } from "../../discovery";
|
|
12
|
+
import { expandPath } from "../../discovery/helpers";
|
|
13
13
|
import * as piCodingAgent from "../../index";
|
|
14
14
|
import { theme } from "../../modes/interactive/theme/theme";
|
|
15
15
|
import type { ExecOptions } from "../exec";
|
|
@@ -19,23 +19,6 @@ import { logger } from "../logger";
|
|
|
19
19
|
import { getAllPluginToolPaths } from "../plugins/loader";
|
|
20
20
|
import type { CustomToolAPI, CustomToolFactory, CustomToolsLoadResult, LoadedCustomTool } from "./types";
|
|
21
21
|
|
|
22
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
23
|
-
|
|
24
|
-
function normalizeUnicodeSpaces(str: string): string {
|
|
25
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function expandPath(p: string): string {
|
|
29
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
30
|
-
if (normalized.startsWith("~/")) {
|
|
31
|
-
return path.join(os.homedir(), normalized.slice(2));
|
|
32
|
-
}
|
|
33
|
-
if (normalized.startsWith("~")) {
|
|
34
|
-
return path.join(os.homedir(), normalized.slice(1));
|
|
35
|
-
}
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
/**
|
|
40
23
|
* Resolve tool path.
|
|
41
24
|
* - Absolute paths used as-is
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import * as path from "node:path";
|
|
8
7
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
9
8
|
import * as TypeBox from "@sinclair/typebox";
|
|
10
9
|
import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
|
|
11
10
|
import { loadCapability } from "../../discovery";
|
|
12
|
-
import { getExtensionNameFromPath } from "../../discovery/helpers";
|
|
11
|
+
import { expandPath, getExtensionNameFromPath } from "../../discovery/helpers";
|
|
13
12
|
import * as piCodingAgent from "../../index";
|
|
14
13
|
import { createEventBus, type EventBus } from "../event-bus";
|
|
15
14
|
import type { ExecOptions } from "../exec";
|
|
@@ -27,23 +26,6 @@ import type {
|
|
|
27
26
|
ToolDefinition,
|
|
28
27
|
} from "./types";
|
|
29
28
|
|
|
30
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
31
|
-
|
|
32
|
-
function normalizeUnicodeSpaces(str: string): string {
|
|
33
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function expandPath(p: string): string {
|
|
37
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
38
|
-
if (normalized.startsWith("~/")) {
|
|
39
|
-
return path.join(homedir(), normalized.slice(2));
|
|
40
|
-
}
|
|
41
|
-
if (normalized.startsWith("~")) {
|
|
42
|
-
return path.join(homedir(), normalized.slice(1));
|
|
43
|
-
}
|
|
44
|
-
return normalized;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
29
|
function resolvePath(extPath: string, cwd: string): string {
|
|
48
30
|
const expanded = expandPath(extPath);
|
|
49
31
|
if (path.isAbsolute(expanded)) {
|
|
@@ -291,7 +273,8 @@ function readExtensionManifest(packageJsonPath: string): ExtensionManifest | nul
|
|
|
291
273
|
return manifest;
|
|
292
274
|
}
|
|
293
275
|
return null;
|
|
294
|
-
} catch {
|
|
276
|
+
} catch (error) {
|
|
277
|
+
logger.warn("Failed to read extension manifest", { path: packageJsonPath, error: String(error) });
|
|
295
278
|
return null;
|
|
296
279
|
}
|
|
297
280
|
}
|
|
@@ -370,7 +353,8 @@ function discoverExtensionsInDir(dir: string): string[] {
|
|
|
370
353
|
}
|
|
371
354
|
}
|
|
372
355
|
}
|
|
373
|
-
} catch {
|
|
356
|
+
} catch (error) {
|
|
357
|
+
logger.warn("Failed to discover extensions in directory", { path: dir, error: String(error) });
|
|
374
358
|
return [];
|
|
375
359
|
}
|
|
376
360
|
|
package/src/core/hooks/loader.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Hook loader - loads TypeScript hook modules using native Bun import.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import * as os from "node:os";
|
|
6
5
|
import * as path from "node:path";
|
|
7
6
|
import * as typebox from "@sinclair/typebox";
|
|
8
7
|
import { hookCapability } from "../../capability/hook";
|
|
9
8
|
import type { Hook } from "../../discovery";
|
|
10
9
|
import { loadCapability } from "../../discovery";
|
|
10
|
+
import { expandPath } from "../../discovery/helpers";
|
|
11
11
|
import * as piCodingAgent from "../../index";
|
|
12
12
|
import { logger } from "../logger";
|
|
13
13
|
import type { HookMessage } from "../messages";
|
|
@@ -84,23 +84,6 @@ export interface LoadHooksResult {
|
|
|
84
84
|
errors: Array<{ path: string; error: string }>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
88
|
-
|
|
89
|
-
function normalizeUnicodeSpaces(str: string): string {
|
|
90
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function expandPath(p: string): string {
|
|
94
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
95
|
-
if (normalized.startsWith("~/")) {
|
|
96
|
-
return path.join(os.homedir(), normalized.slice(2));
|
|
97
|
-
}
|
|
98
|
-
if (normalized.startsWith("~")) {
|
|
99
|
-
return path.join(os.homedir(), normalized.slice(1));
|
|
100
|
-
}
|
|
101
|
-
return normalized;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
87
|
/**
|
|
105
88
|
* Resolve hook path.
|
|
106
89
|
* - Absolute paths used as-is
|
package/src/core/keybindings.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
setEditorKeybindings,
|
|
11
11
|
} from "@oh-my-pi/pi-tui";
|
|
12
12
|
import { getAgentDir } from "../config";
|
|
13
|
+
import { logger } from "./logger";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Application-level actions (coding agent specific).
|
|
@@ -136,7 +137,8 @@ export class KeybindingsManager {
|
|
|
136
137
|
if (!existsSync(path)) return {};
|
|
137
138
|
try {
|
|
138
139
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
139
|
-
} catch {
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.warn("Failed to parse keybindings config", { path, error: String(error) });
|
|
140
142
|
return {};
|
|
141
143
|
}
|
|
142
144
|
}
|
package/src/core/logger.ts
CHANGED
|
@@ -10,11 +10,10 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import winston from "winston";
|
|
12
12
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
13
|
-
import { CONFIG_DIR_NAME } from "../config";
|
|
14
13
|
|
|
15
14
|
/** Get the logs directory (~/.omp/logs/) */
|
|
16
15
|
function getLogsDir(): string {
|
|
17
|
-
return join(homedir(),
|
|
16
|
+
return join(homedir(), ".omp", "logs");
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
/** Ensure logs directory exists */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
2
|
import Handlebars from "handlebars";
|
|
3
3
|
import { CONFIG_DIR_NAME, getPromptsDir } from "../config";
|
|
4
|
+
import { logger } from "./logger";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents a prompt template loaded from a markdown file
|
|
@@ -448,12 +449,12 @@ async function loadTemplatesFromDir(
|
|
|
448
449
|
source: sourceStr,
|
|
449
450
|
});
|
|
450
451
|
}
|
|
451
|
-
} catch (
|
|
452
|
-
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logger.warn("Failed to load prompt template", { path: fullPath, error: String(error) });
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
|
-
} catch (
|
|
456
|
-
|
|
456
|
+
} catch (error) {
|
|
457
|
+
logger.warn("Failed to scan prompt templates directory", { dir, error: String(error) });
|
|
457
458
|
}
|
|
458
459
|
|
|
459
460
|
return templates;
|
package/src/core/sdk.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import { join } from "node:path";
|
|
30
30
|
import { Agent, type AgentEvent, type AgentMessage, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
31
|
-
import type
|
|
31
|
+
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
32
32
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
33
33
|
import chalk from "chalk";
|
|
34
34
|
// Import discovery to register all providers on startup
|
|
@@ -631,6 +631,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
631
631
|
// Clamp to model capabilities
|
|
632
632
|
if (!model || !model.reasoning) {
|
|
633
633
|
thinkingLevel = "off";
|
|
634
|
+
} else if (thinkingLevel === "xhigh" && !supportsXhigh(model)) {
|
|
635
|
+
thinkingLevel = "high";
|
|
634
636
|
}
|
|
635
637
|
|
|
636
638
|
let skills: Skill[];
|
|
@@ -1021,8 +1023,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1021
1023
|
});
|
|
1022
1024
|
lspServers = result.servers;
|
|
1023
1025
|
time("warmupLspServers");
|
|
1024
|
-
} catch {
|
|
1025
|
-
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
logger.warn("LSP server warmup failed", { cwd, error: String(error) });
|
|
1026
1028
|
}
|
|
1027
1029
|
}
|
|
1028
1030
|
|
package/src/core/skills.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { SourceMeta } from "../capability/types";
|
|
|
7
7
|
import type { Skill as CapabilitySkill, SkillFrontmatter as ImportedSkillFrontmatter } from "../discovery";
|
|
8
8
|
import { loadCapability } from "../discovery";
|
|
9
9
|
import { parseFrontmatter } from "../discovery/helpers";
|
|
10
|
+
import { logger } from "./logger";
|
|
10
11
|
import type { SkillsSettings } from "./settings-manager";
|
|
11
12
|
|
|
12
13
|
// Re-export SkillFrontmatter for backward compatibility
|
|
@@ -67,8 +68,8 @@ export function loadSkillsFromDir(options: LoadSkillsFromDirOptions): LoadSkills
|
|
|
67
68
|
source: options.source,
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
|
-
} catch {
|
|
71
|
-
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.warn("Failed to load skill", { path: skillFile, error: String(error) });
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -131,8 +132,8 @@ function scanDirectoryForSkills(dir: string): LoadSkillsResult {
|
|
|
131
132
|
source: "custom",
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
|
-
} catch {
|
|
135
|
-
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.warn("Failed to load skill", { path: skillFile, error: String(error) });
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|
package/src/core/tools/bash.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { resolveToCwd } from "./path-utils";
|
|
|
15
15
|
import { createToolUIKit } from "./render-utils";
|
|
16
16
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
17
17
|
|
|
18
|
+
export const BASH_DEFAULT_PREVIEW_LINES = 10;
|
|
19
|
+
|
|
18
20
|
const bashSchema = Type.Object({
|
|
19
21
|
command: Type.String({ description: "Bash command to execute" }),
|
|
20
22
|
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
@@ -26,6 +28,7 @@ const bashSchema = Type.Object({
|
|
|
26
28
|
export interface BashToolDetails {
|
|
27
29
|
truncation?: TruncationResult;
|
|
28
30
|
fullOutputPath?: string;
|
|
31
|
+
fullOutput?: string;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -101,9 +104,12 @@ export function createBashTool(session: ToolSession, options?: BashToolOptions):
|
|
|
101
104
|
const truncation = truncateTail(currentOutput);
|
|
102
105
|
onUpdate({
|
|
103
106
|
content: [{ type: "text", text: truncation.content || "" }],
|
|
104
|
-
details:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
details: truncation.truncated
|
|
108
|
+
? {
|
|
109
|
+
truncation,
|
|
110
|
+
fullOutput: currentOutput,
|
|
111
|
+
}
|
|
112
|
+
: undefined,
|
|
107
113
|
});
|
|
108
114
|
}
|
|
109
115
|
},
|
|
@@ -129,6 +135,7 @@ export function createBashTool(session: ToolSession, options?: BashToolOptions):
|
|
|
129
135
|
details = {
|
|
130
136
|
truncation,
|
|
131
137
|
fullOutputPath: result.fullOutputPath,
|
|
138
|
+
fullOutput: currentOutput,
|
|
132
139
|
};
|
|
133
140
|
|
|
134
141
|
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
@@ -173,6 +180,9 @@ interface BashRenderContext {
|
|
|
173
180
|
previewLines?: number;
|
|
174
181
|
}
|
|
175
182
|
|
|
183
|
+
// Preview line limit when not expanded (matches tool-execution behavior)
|
|
184
|
+
export const BASH_PREVIEW_LINES = 10;
|
|
185
|
+
|
|
176
186
|
export const bashToolRenderer = {
|
|
177
187
|
renderCall(args: BashRenderArgs, uiTheme: Theme): Component {
|
|
178
188
|
const ui = createToolUIKit(uiTheme);
|
|
@@ -214,21 +224,25 @@ export const bashToolRenderer = {
|
|
|
214
224
|
const { renderContext } = options;
|
|
215
225
|
const details = result.details;
|
|
216
226
|
|
|
227
|
+
const expanded = renderContext?.expanded ?? options.expanded;
|
|
228
|
+
const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
|
|
229
|
+
|
|
217
230
|
// Get output from context (preferred) or fall back to result content
|
|
218
231
|
const output = renderContext?.output ?? (result.content?.find((c) => c.type === "text")?.text ?? "").trim();
|
|
219
|
-
const
|
|
220
|
-
const
|
|
232
|
+
const fullOutput = details?.fullOutput;
|
|
233
|
+
const displayOutput = expanded ? (fullOutput ?? output) : output;
|
|
234
|
+
const showingFullOutput = expanded && fullOutput !== undefined;
|
|
221
235
|
|
|
222
236
|
// Build truncation warning lines (static, doesn't depend on width)
|
|
223
237
|
const truncation = details?.truncation;
|
|
224
238
|
const fullOutputPath = details?.fullOutputPath;
|
|
225
239
|
let warningLine: string | undefined;
|
|
226
|
-
if (truncation?.truncated
|
|
240
|
+
if (fullOutputPath || (truncation?.truncated && !showingFullOutput)) {
|
|
227
241
|
const warnings: string[] = [];
|
|
228
242
|
if (fullOutputPath) {
|
|
229
243
|
warnings.push(`Full output: ${fullOutputPath}`);
|
|
230
244
|
}
|
|
231
|
-
if (truncation?.truncated) {
|
|
245
|
+
if (truncation?.truncated && !showingFullOutput) {
|
|
232
246
|
if (truncation.truncatedBy === "lines") {
|
|
233
247
|
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
|
|
234
248
|
} else {
|
|
@@ -237,17 +251,19 @@ export const bashToolRenderer = {
|
|
|
237
251
|
);
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
|
-
|
|
254
|
+
if (warnings.length > 0) {
|
|
255
|
+
warningLine = uiTheme.fg("warning", ui.wrapBrackets(warnings.join(". ")));
|
|
256
|
+
}
|
|
241
257
|
}
|
|
242
258
|
|
|
243
|
-
if (!
|
|
259
|
+
if (!displayOutput) {
|
|
244
260
|
// No output - just show warning if any
|
|
245
261
|
return new Text(warningLine ?? "", 0, 0);
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
if (expanded) {
|
|
249
265
|
// Show all lines when expanded
|
|
250
|
-
const styledOutput =
|
|
266
|
+
const styledOutput = displayOutput
|
|
251
267
|
.split("\n")
|
|
252
268
|
.map((line) => uiTheme.fg("toolOutput", line))
|
|
253
269
|
.join("\n");
|
|
@@ -256,7 +272,7 @@ export const bashToolRenderer = {
|
|
|
256
272
|
}
|
|
257
273
|
|
|
258
274
|
// Collapsed: use width-aware caching component
|
|
259
|
-
const styledOutput =
|
|
275
|
+
const styledOutput = displayOutput
|
|
260
276
|
.split("\n")
|
|
261
277
|
.map((line) => uiTheme.fg("toolOutput", line))
|
|
262
278
|
.join("\n");
|
|
@@ -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
|
}
|
|
@@ -359,11 +359,11 @@ export function formatDiagnostics(
|
|
|
359
359
|
const isLastDiag = di === diagnostics.length - 1;
|
|
360
360
|
const diagBranch = isLastFile
|
|
361
361
|
? isLastDiag
|
|
362
|
-
? `
|
|
363
|
-
: `
|
|
362
|
+
? ` ${theme.tree.last}`
|
|
363
|
+
: ` ${theme.tree.branch}`
|
|
364
364
|
: isLastDiag
|
|
365
|
-
?
|
|
366
|
-
:
|
|
365
|
+
? `${theme.tree.vertical} ${theme.tree.last}`
|
|
366
|
+
: `${theme.tree.vertical} ${theme.tree.branch}`;
|
|
367
367
|
|
|
368
368
|
const sevIcon =
|
|
369
369
|
d.severity === "error"
|
|
@@ -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}`,
|