mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Update files
This commit is contained in:
@@ -363,6 +363,7 @@ export function AgentStreamView({
|
||||
return (
|
||||
<ToolCall
|
||||
toolName={data.name}
|
||||
provider={data.provider}
|
||||
args={data.input}
|
||||
result={data.result}
|
||||
error={data.error}
|
||||
@@ -907,8 +908,12 @@ function PermissionRequestCard({
|
||||
if (isPlanRequest) {
|
||||
return null;
|
||||
}
|
||||
return parseToolCallDisplay({ name: request.name ?? "unknown", input: request.input });
|
||||
}, [isPlanRequest, request.name, request.input]);
|
||||
return parseToolCallDisplay({
|
||||
name: request.name ?? "unknown",
|
||||
provider: request.provider,
|
||||
input: request.input,
|
||||
});
|
||||
}, [isPlanRequest, request.name, request.provider, request.input]);
|
||||
|
||||
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [theme]);
|
||||
|
||||
|
||||
@@ -420,7 +420,6 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 100,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1017,7 +1016,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
if (isLoading) {
|
||||
shimmer.value = -1;
|
||||
shimmer.value = withRepeat(
|
||||
withTiming(1, { duration: 2400, easing: Easing.bezier(0.4, 0, 0.6, 1) }),
|
||||
withTiming(1, { duration: 3200, easing: Easing.linear }),
|
||||
-1
|
||||
);
|
||||
} else {
|
||||
@@ -1026,7 +1025,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
}
|
||||
}, [isLoading]);
|
||||
|
||||
const shimmerBandWidth = 100;
|
||||
const shimmerBandWidth = 14;
|
||||
const shimmerStyle = useAnimatedStyle(() => {
|
||||
const travel = badgeWidth + shimmerBandWidth;
|
||||
return {
|
||||
@@ -1098,7 +1097,11 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
{isLoading && badgeWidth > 0 ? (
|
||||
<Animated.View
|
||||
pointerEvents="none"
|
||||
style={[expandableBadgeStylesheet.shimmerOverlay, shimmerStyle]}
|
||||
style={[
|
||||
expandableBadgeStylesheet.shimmerOverlay,
|
||||
{ width: shimmerBandWidth },
|
||||
shimmerStyle,
|
||||
]}
|
||||
>
|
||||
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
||||
<Defs>
|
||||
@@ -1109,10 +1112,13 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
x2="1"
|
||||
y2="0"
|
||||
>
|
||||
<Stop offset="0" stopColor={theme.colors.surface1} stopOpacity="0" />
|
||||
<Stop offset="0.35" stopColor={theme.colors.surface1} stopOpacity="1" />
|
||||
<Stop offset="0.65" stopColor={theme.colors.surface1} stopOpacity="1" />
|
||||
<Stop offset="1" stopColor={theme.colors.surface1} stopOpacity="0" />
|
||||
<Stop offset="0" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="0.34" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="0.46" stopColor={baseColors.white} stopOpacity="0.12" />
|
||||
<Stop offset="0.5" stopColor={baseColors.white} stopOpacity="0.34" />
|
||||
<Stop offset="0.54" stopColor={baseColors.white} stopOpacity="0.12" />
|
||||
<Stop offset="0.66" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="1" stopColor={baseColors.white} stopOpacity="0" />
|
||||
</LinearGradient>
|
||||
</Defs>
|
||||
<Rect width="100%" height="100%" fill="url(#shimmerGrad)" />
|
||||
@@ -1149,6 +1155,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
|
||||
interface ToolCallProps {
|
||||
toolName: string;
|
||||
provider?: string;
|
||||
args: any;
|
||||
result?: any;
|
||||
error?: any;
|
||||
@@ -1176,6 +1183,7 @@ const TOOL_CALL_COMMIT_THRESHOLD_MS = 16;
|
||||
|
||||
export const ToolCall = memo(function ToolCall({
|
||||
toolName,
|
||||
provider,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
@@ -1197,8 +1205,17 @@ export const ToolCall = memo(function ToolCall({
|
||||
UnistylesRuntime.breakpoint === "sm";
|
||||
|
||||
const displayInfo = useMemo(
|
||||
() => parseToolCallDisplay({ name: toolName, input: args, output: result, error, metadata, cwd }),
|
||||
[toolName, args, result, error, metadata, cwd]
|
||||
() =>
|
||||
parseToolCallDisplay({
|
||||
name: toolName,
|
||||
provider,
|
||||
input: args,
|
||||
output: result,
|
||||
error,
|
||||
metadata,
|
||||
cwd,
|
||||
}),
|
||||
[toolName, provider, args, result, error, metadata, cwd]
|
||||
);
|
||||
const { kind, displayName, summary, detail, errorText } = displayInfo;
|
||||
const IconComponent = toolKindIcons[kind] || Wrench;
|
||||
|
||||
@@ -384,6 +384,54 @@ function testToolCallParsedPayloadHydration() {
|
||||
assert.ok(commandPass, 'Command payload should persist across hydration');
|
||||
}
|
||||
|
||||
function testNullToolPayloadDoesNotEraseKnownInput() {
|
||||
const timestampStart = new Date("2025-01-01T10:36:00Z");
|
||||
const timestampFinish = new Date("2025-01-01T10:36:05Z");
|
||||
const callId = "null-input-preserve";
|
||||
|
||||
const updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }> = [
|
||||
{
|
||||
event: {
|
||||
type: "timeline",
|
||||
provider: "claude",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
name: "shell",
|
||||
status: "pending",
|
||||
callId,
|
||||
input: { command: "pwd" },
|
||||
},
|
||||
},
|
||||
timestamp: timestampStart,
|
||||
},
|
||||
{
|
||||
event: {
|
||||
type: "timeline",
|
||||
provider: "claude",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
name: "shell",
|
||||
status: "completed",
|
||||
callId,
|
||||
input: null,
|
||||
output: { type: "command", output: "/tmp" },
|
||||
},
|
||||
},
|
||||
timestamp: timestampFinish,
|
||||
},
|
||||
];
|
||||
|
||||
const state = hydrateStreamState(updates);
|
||||
const commandEntry = state.find(
|
||||
(item): item is AgentToolCallItem =>
|
||||
isAgentToolCallItem(item) && item.payload.data.callId === callId
|
||||
);
|
||||
|
||||
assert.ok(commandEntry, "Tool call should exist");
|
||||
assert.strictEqual(commandEntry.payload.data.status, "completed");
|
||||
assert.deepStrictEqual(commandEntry.payload.data.input, { command: "pwd" });
|
||||
}
|
||||
|
||||
function buildClaudeToolUseBlock({
|
||||
id,
|
||||
name,
|
||||
@@ -1116,6 +1164,7 @@ describe('stream timeline reducers', () => {
|
||||
it('infers failure from error payloads', testToolCallFailureInferenceFromError);
|
||||
it('reconciles late call IDs against pending entries', testToolCallLateCallIdReconciliation);
|
||||
it('persists parsed read/edit/command payloads after hydration', testToolCallParsedPayloadHydration);
|
||||
it('preserves known input when later tool updates send null input', testNullToolPayloadDoesNotEraseKnownInput);
|
||||
it('hydrates Claude tool bodies with parsed content', testClaudeHydratedToolBodies);
|
||||
it('preserves whitespace in assistant chunk concatenation', testAssistantWhitespacePreservation);
|
||||
it('hydrates user messages and deduplicates optimistic/live entries', testUserMessageHydration);
|
||||
|
||||
@@ -431,15 +431,15 @@ function appendAgentToolCall(
|
||||
const next = [...state];
|
||||
const existing = next[existingIndex] as AgentToolCallItem;
|
||||
const mergedInput =
|
||||
payloadData.input !== undefined
|
||||
hasValue(payloadData.input)
|
||||
? payloadData.input
|
||||
: existing.payload.data.input;
|
||||
const mergedResult =
|
||||
payloadData.result !== undefined
|
||||
hasValue(payloadData.result)
|
||||
? payloadData.result
|
||||
: existing.payload.data.result;
|
||||
const mergedError =
|
||||
payloadData.error !== undefined
|
||||
hasValue(payloadData.error)
|
||||
? payloadData.error
|
||||
: existing.payload.data.error;
|
||||
const mergedStatus = mergeToolCallStatus(
|
||||
|
||||
@@ -64,6 +64,35 @@ describe("parseToolCallDisplay", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("falls back to output command when shell input is missing", () => {
|
||||
const output = { type: "command", command: "pwd", output: "/some/path" };
|
||||
|
||||
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Bash", output });
|
||||
expect(info.summary).toBe("pwd");
|
||||
expect(info.detail.type).toBe("shell");
|
||||
if (info.detail.type === "shell") {
|
||||
expect(info.detail.command).toBe("pwd");
|
||||
expect(info.detail.output).toBe("/some/path");
|
||||
}
|
||||
});
|
||||
|
||||
test("falls back to read output path when input is missing", () => {
|
||||
const info: ToolCallDisplayInfo = parseToolCallDisplay({
|
||||
name: "Read",
|
||||
output: {
|
||||
type: "file_read",
|
||||
filePath: "/some/file.txt",
|
||||
content: "hello",
|
||||
},
|
||||
});
|
||||
expect(info.summary).toBe("/some/file.txt");
|
||||
expect(info.detail.type).toBe("read");
|
||||
if (info.detail.type === "read") {
|
||||
expect(info.detail.filePath).toBe("/some/file.txt");
|
||||
expect(info.detail.content).toBe("hello");
|
||||
}
|
||||
});
|
||||
|
||||
test("strips shell + cd wrapper from command (Codex exec_command style)", () => {
|
||||
const input = {
|
||||
command:
|
||||
@@ -110,6 +139,14 @@ describe("parseToolCallDisplay", () => {
|
||||
expect(info.displayName).toBe("Read");
|
||||
});
|
||||
|
||||
test("uses stable label for Edit in frontend", () => {
|
||||
const input = { file_path: "/some/file.txt", old_string: "a", new_string: "b" };
|
||||
const first = parseToolCallDisplay({ name: "Edit", input });
|
||||
const second = parseToolCallDisplay({ name: "Edit", input });
|
||||
expect(first.displayName).toBe("Edit");
|
||||
expect(second.displayName).toBe("Edit");
|
||||
});
|
||||
|
||||
test("normalizes tool names - paseo_voice.speak to Speak", () => {
|
||||
const input = { text: "hello from namespaced speak" };
|
||||
const info = parseToolCallDisplay({ name: "paseo_voice.speak", input });
|
||||
@@ -128,6 +165,15 @@ describe("parseToolCallDisplay", () => {
|
||||
expect(info.displayName).toBe("MyCustomTool");
|
||||
});
|
||||
|
||||
test("does not let Task metadata override non-Task summary", () => {
|
||||
const info = parseToolCallDisplay({
|
||||
name: "shell",
|
||||
input: { command: "pwd" },
|
||||
metadata: { subAgentActivity: "Read" },
|
||||
});
|
||||
expect(info.summary).toBe("pwd");
|
||||
});
|
||||
|
||||
test("parses non-command tool call into generic detail", () => {
|
||||
const input = { file_path: "/some/file.txt" };
|
||||
const output = { content: "file contents here", lineCount: 42 };
|
||||
@@ -141,12 +187,16 @@ describe("parseToolCallDisplay", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("handles file_write output as generic", () => {
|
||||
test("handles file_write output as edit", () => {
|
||||
const input = { file_path: "/some/file.txt", content: "new content" };
|
||||
const output = { type: "file_write", filePath: "/some/file.txt" };
|
||||
|
||||
const info: ToolCallDisplayInfo = parseToolCallDisplay({ name: "Write", input, output });
|
||||
expect(info.detail.type).toBe("generic");
|
||||
expect(info.detail.type).toBe("edit");
|
||||
expect(info.summary).toBe("/some/file.txt");
|
||||
if (info.detail.type === "edit") {
|
||||
expect(info.detail.filePath).toBe("/some/file.txt");
|
||||
}
|
||||
});
|
||||
|
||||
test("handles undefined input and output gracefully", () => {
|
||||
@@ -366,14 +416,13 @@ describe("parseToolCallDisplay - read_file (Codex)", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("Codex read_file falls through to generic when result is missing", () => {
|
||||
test("Codex read_file stays read when result is missing", () => {
|
||||
const input = {
|
||||
path: "/some/file.txt",
|
||||
};
|
||||
|
||||
const info = parseToolCallDisplay({ name: "read_file", input });
|
||||
// Without result, it can't match the schema so falls through to generic
|
||||
expect(info.detail.type).toBe("generic");
|
||||
expect(info.detail.type).toBe("read");
|
||||
expect(info.displayName).toBe("Read");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"generate:config-schema": "tsx scripts/generate-config-schema.ts",
|
||||
"speech:models": "tsx scripts/list-speech-models.ts",
|
||||
"speech:download": "tsx scripts/download-speech-models.ts",
|
||||
"speech:transcribe:local": "tsx scripts/transcribe-local-wav.ts",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
|
||||
180
packages/server/scripts/transcribe-local-wav.ts
Normal file
180
packages/server/scripts/transcribe-local-wav.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { STTManager } from "../src/server/agent/stt-manager.js";
|
||||
import { createRootLogger } from "../src/server/logger.js";
|
||||
import { resolvePaseoHome } from "../src/server/paseo-home.js";
|
||||
import {
|
||||
DEFAULT_LOCAL_STT_MODEL,
|
||||
DEFAULT_LOCAL_TTS_MODEL,
|
||||
LocalSttModelIdSchema,
|
||||
type LocalSttModelId,
|
||||
} from "../src/server/speech/providers/local/models.js";
|
||||
import { initializeLocalSpeechServices } from "../src/server/speech/providers/local/runtime.js";
|
||||
import type { RequestedSpeechProviders } from "../src/server/speech/speech-types.js";
|
||||
|
||||
type CliOptions = {
|
||||
wavPath: string;
|
||||
outPath?: string;
|
||||
model: LocalSttModelId;
|
||||
modelsDir: string;
|
||||
autoDownload: boolean;
|
||||
};
|
||||
|
||||
function usage(): string {
|
||||
return [
|
||||
"Usage: npm run speech:transcribe:local -- <wavPath> [--out <outPath>] [--model <modelId>] [--models-dir <dir>] [--no-auto-download]",
|
||||
"",
|
||||
"Examples:",
|
||||
" npm run speech:transcribe:local -- ./sample.wav",
|
||||
" npm run speech:transcribe:local -- ./sample.wav --out ./tmp/sample.transcript.txt",
|
||||
"",
|
||||
"Env fallbacks:",
|
||||
" PASEO_LOCAL_MODELS_DIR, PASEO_LOCAL_STT_MODEL",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]): CliOptions {
|
||||
if (argv.includes("--help") || argv.includes("-h")) {
|
||||
process.stdout.write(`${usage()}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (argv.length === 0) {
|
||||
throw new Error(`Missing <wavPath>\n\n${usage()}`);
|
||||
}
|
||||
|
||||
const paseoHome = resolvePaseoHome();
|
||||
const defaultModelsDir =
|
||||
process.env.PASEO_LOCAL_MODELS_DIR ?? path.join(paseoHome, "models", "local-speech");
|
||||
|
||||
const positional: string[] = [];
|
||||
let outPath: string | undefined;
|
||||
let model = LocalSttModelIdSchema.parse(process.env.PASEO_LOCAL_STT_MODEL ?? DEFAULT_LOCAL_STT_MODEL);
|
||||
let modelsDir = defaultModelsDir;
|
||||
let autoDownload = true;
|
||||
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
|
||||
if (arg === "--out") {
|
||||
const next = argv[i + 1];
|
||||
if (!next) {
|
||||
throw new Error("--out requires a value");
|
||||
}
|
||||
outPath = path.resolve(next);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--model") {
|
||||
const next = argv[i + 1];
|
||||
if (!next) {
|
||||
throw new Error("--model requires a value");
|
||||
}
|
||||
model = LocalSttModelIdSchema.parse(next);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--models-dir") {
|
||||
const next = argv[i + 1];
|
||||
if (!next) {
|
||||
throw new Error("--models-dir requires a value");
|
||||
}
|
||||
modelsDir = path.resolve(next);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--no-auto-download") {
|
||||
autoDownload = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith("-")) {
|
||||
throw new Error(`Unknown option: ${arg}`);
|
||||
}
|
||||
|
||||
positional.push(arg);
|
||||
}
|
||||
|
||||
if (positional.length === 0) {
|
||||
throw new Error(`Missing <wavPath>\n\n${usage()}`);
|
||||
}
|
||||
|
||||
return {
|
||||
wavPath: path.resolve(positional[0]),
|
||||
...(outPath ? { outPath } : {}),
|
||||
model,
|
||||
modelsDir,
|
||||
autoDownload,
|
||||
};
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
let options: CliOptions;
|
||||
|
||||
try {
|
||||
options = parseArgs(process.argv.slice(2));
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exit(2);
|
||||
return;
|
||||
}
|
||||
|
||||
const logger = createRootLogger({ level: "info", format: "pretty" });
|
||||
|
||||
const providers: RequestedSpeechProviders = {
|
||||
dictationStt: { provider: "local", explicit: true },
|
||||
voiceStt: { provider: "local", explicit: true },
|
||||
// Not used here, but required by the shared runtime config shape.
|
||||
voiceTts: { provider: "openai", explicit: false },
|
||||
};
|
||||
|
||||
const runtime = await initializeLocalSpeechServices({
|
||||
providers,
|
||||
speechConfig: {
|
||||
providers,
|
||||
local: {
|
||||
modelsDir: options.modelsDir,
|
||||
autoDownload: options.autoDownload,
|
||||
models: {
|
||||
dictationStt: options.model,
|
||||
voiceStt: options.model,
|
||||
voiceTts: DEFAULT_LOCAL_TTS_MODEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
logger,
|
||||
});
|
||||
|
||||
try {
|
||||
if (!runtime.sttService) {
|
||||
throw new Error(
|
||||
"Local STT service is unavailable. Check model files or run `npm run speech:download -- --model " +
|
||||
options.model +
|
||||
"`."
|
||||
);
|
||||
}
|
||||
|
||||
const audio = await readFile(options.wavPath);
|
||||
const manager = new STTManager("dev-local-wav-transcribe", logger, runtime.sttService);
|
||||
const result = await manager.transcribe(audio, "audio/wav", { label: "dev-local-wav-transcribe" });
|
||||
|
||||
const transcript = result.text.trim();
|
||||
|
||||
if (options.outPath) {
|
||||
await mkdir(path.dirname(options.outPath), { recursive: true });
|
||||
await writeFile(options.outPath, `${transcript}\n`, "utf8");
|
||||
logger.info({ outPath: options.outPath }, "Wrote transcript");
|
||||
}
|
||||
|
||||
process.stdout.write(`${transcript}\n`);
|
||||
} finally {
|
||||
runtime.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
@@ -159,7 +159,11 @@ export function curateAgentActivity(
|
||||
case "tool_call": {
|
||||
flushBuffers(lines, buffers);
|
||||
const inputJson = formatToolInputJson(item.input);
|
||||
const { displayName, summary } = parseToolCallDisplay({ name: item.name, input: item.input, metadata: item.metadata });
|
||||
const { displayName, summary } = parseToolCallDisplay({
|
||||
name: item.name,
|
||||
input: item.input,
|
||||
metadata: item.metadata,
|
||||
});
|
||||
if (isLikelyExternalToolName(item.name) && inputJson) {
|
||||
lines.push(`[${displayName}] ${inputJson}`);
|
||||
break;
|
||||
|
||||
@@ -98,6 +98,7 @@ describe("parseToolCallDisplay", () => {
|
||||
describe("summary (was extractPrincipalParam)", () => {
|
||||
test("extracts and strips shell wrapper from command string", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
provider: "claude",
|
||||
name: "Bash",
|
||||
input: { command: "/bin/zsh -lc cd /Users/dev/project && npm run format" },
|
||||
});
|
||||
@@ -120,6 +121,14 @@ describe("parseToolCallDisplay", () => {
|
||||
expect(result.summary).toBe("npm run build");
|
||||
});
|
||||
|
||||
test("falls back to output command when shell input is missing", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
name: "Bash",
|
||||
output: { type: "command", command: "pwd", output: "/tmp" },
|
||||
});
|
||||
expect(result.summary).toBe("pwd");
|
||||
});
|
||||
|
||||
test("extracts file_path and strips cwd", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
name: "Read",
|
||||
@@ -129,6 +138,34 @@ describe("parseToolCallDisplay", () => {
|
||||
expect(result.summary).toBe("src/file.ts");
|
||||
});
|
||||
|
||||
test("falls back to read output path when input is missing", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
provider: "codex",
|
||||
name: "Read",
|
||||
output: {
|
||||
type: "file_read",
|
||||
filePath: "/Users/dev/project/src/file.ts",
|
||||
content: "hello",
|
||||
},
|
||||
cwd: "/Users/dev/project",
|
||||
});
|
||||
expect(result.summary).toBe("src/file.ts");
|
||||
});
|
||||
|
||||
test("falls back to edit output path when input is missing", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
name: "Edit",
|
||||
output: {
|
||||
type: "file_edit",
|
||||
filePath: "/Users/dev/project/src/file.ts",
|
||||
oldContent: "a",
|
||||
newContent: "b",
|
||||
},
|
||||
cwd: "/Users/dev/project",
|
||||
});
|
||||
expect(result.summary).toBe("src/file.ts");
|
||||
});
|
||||
|
||||
test("extracts pattern without modification", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
name: "Grep",
|
||||
@@ -168,7 +205,7 @@ describe("parseToolCallDisplay", () => {
|
||||
});
|
||||
|
||||
describe("summary from metadata", () => {
|
||||
test("subAgentActivity in metadata takes priority over input", () => {
|
||||
test("subAgentActivity in metadata takes priority for Task", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
name: "Task",
|
||||
input: { description: "Explore codebase" },
|
||||
@@ -185,6 +222,15 @@ describe("parseToolCallDisplay", () => {
|
||||
});
|
||||
expect(result.summary).toBe("Bash");
|
||||
});
|
||||
|
||||
test("non-Task tools keep their parsed summary even when metadata is present", () => {
|
||||
const result = parseToolCallDisplay({
|
||||
name: "Bash",
|
||||
input: { command: "pwd" },
|
||||
metadata: { subAgentActivity: "Read" },
|
||||
});
|
||||
expect(result.summary).toBe("pwd");
|
||||
});
|
||||
});
|
||||
|
||||
describe("kind", () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
4
paseo@0.1.0
Normal file
4
paseo@0.1.0
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
> paseo@0.1.0 cli
|
||||
> npx tsx packages/cli/src/index.js logs cli --filter tools
|
||||
|
||||
Reference in New Issue
Block a user