Update files

This commit is contained in:
Mohamed Boudra
2026-02-07 14:36:30 +07:00
parent ed32394acd
commit 5d890ea0b8
12 changed files with 168 additions and 114 deletions
@@ -357,7 +357,7 @@ describe("curateAgentActivity", () => {
];
const result = curateAgentActivity(timeline);
expect(result).toBe('[speak] {"text":"hello from voice"}');
expect(result).toBe('[Speak] {"text":"hello from voice"}');
});
test("shows MCP tool input JSON", () => {
@@ -389,7 +389,22 @@ describe("curateAgentActivity", () => {
];
const result = curateAgentActivity(timeline);
expect(result).toBe('[paseo_voice.speak] {"text":"hello from namespaced tool"}');
expect(result).toBe('[Speak] {"text":"hello from namespaced tool"}');
});
test("shows claude mcp speak tool input as Speak", () => {
const timeline: AgentTimelineItem[] = [
{
type: "tool_call",
callId: "m3",
name: "mcp__paseo_voice__speak",
input: { text: "hello from claude mcp" },
status: "completed",
},
];
const result = curateAgentActivity(timeline);
expect(result).toBe('[Speak] {"text":"hello from claude mcp"}');
});
});
});
@@ -1,5 +1,5 @@
import type { AgentTimelineItem } from "./agent-sdk-types.js";
import { extractPrincipalParam } from "../../utils/tool-call-parsers.js";
import { extractPrincipalParam, normalizeToolDisplayName } from "../../utils/tool-call-parsers.js";
import { isLikelyExternalToolName } from "./tool-name-normalization.js";
const DEFAULT_MAX_ITEMS = 40;
@@ -147,16 +147,17 @@ export function curateAgentActivity(
break;
case "tool_call": {
flushBuffers(lines, buffers);
const displayName = normalizeToolDisplayName(item.name);
const inputJson = formatToolInputJson(item.input);
if (isLikelyExternalToolName(item.name) && inputJson) {
lines.push(`[${item.name}] ${inputJson}`);
lines.push(`[${displayName}] ${inputJson}`);
break;
}
const principal = extractPrincipalParam(item.input);
if (principal) {
lines.push(`[${item.name}] ${principal}`);
lines.push(`[${displayName}] ${principal}`);
} else {
lines.push(`[${item.name}]`);
lines.push(`[${displayName}]`);
}
break;
}
@@ -1350,7 +1350,15 @@ class CodexAppServerAgentSession implements AgentSession {
if (ids.includes(this.currentThreadId)) {
return;
}
await this.client.request("thread/resume", { threadId: this.currentThreadId });
const params: Record<string, unknown> = { threadId: this.currentThreadId };
if (this.config.systemPrompt?.trim()) {
params.developerInstructions = this.config.systemPrompt.trim();
}
const codexConfig = this.buildCodexInnerConfig();
if (codexConfig) {
params.config = codexConfig;
}
await this.client.request("thread/resume", params);
} catch (error) {
this.logger.warn({ error }, "Failed to resume Codex thread, starting new thread");
this.currentThreadId = null;
@@ -1438,6 +1446,13 @@ class CodexAppServerAgentSession implements AgentSession {
if (options?.outputSchema) {
params.outputSchema = options.outputSchema;
}
if (this.config.systemPrompt?.trim()) {
params.developerInstructions = this.config.systemPrompt.trim();
}
const codexConfig = this.buildCodexInnerConfig();
if (codexConfig) {
params.config = codexConfig;
}
await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
@@ -1707,6 +1722,25 @@ class CodexAppServerAgentSession implements AgentSession {
const preset = MODE_PRESETS[this.currentMode] ?? MODE_PRESETS[DEFAULT_CODEX_MODE_ID];
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
const sandbox = this.config.sandboxMode ?? preset.sandbox;
const innerConfig = this.buildCodexInnerConfig();
const response = (await this.client.request("thread/start", {
model,
cwd: this.config.cwd ?? null,
approvalPolicy,
sandbox,
...(this.config.systemPrompt?.trim()
? { developerInstructions: this.config.systemPrompt.trim() }
: {}),
...(innerConfig ? { config: innerConfig } : {}),
})) as CodexThreadStartResponse;
const threadId = response?.thread?.id;
if (!threadId) {
throw new Error("Codex app-server did not return thread id");
}
this.currentThreadId = threadId;
}
private buildCodexInnerConfig(): Record<string, unknown> | null {
const innerConfig: Record<string, unknown> = {};
if (this.config.mcpServers) {
const mcpServers: Record<string, CodexMcpServerConfig> = {};
@@ -1718,21 +1752,7 @@ class CodexAppServerAgentSession implements AgentSession {
if (this.config.extra?.codex) {
Object.assign(innerConfig, this.config.extra.codex);
}
const response = (await this.client.request("thread/start", {
model,
cwd: this.config.cwd ?? null,
approvalPolicy,
sandbox,
...(this.config.systemPrompt?.trim()
? { developerInstructions: this.config.systemPrompt.trim() }
: {}),
...(Object.keys(innerConfig).length > 0 ? { config: innerConfig } : {}),
})) as CodexThreadStartResponse;
const threadId = response?.thread?.id;
if (!threadId) {
throw new Error("Codex app-server did not return thread id");
}
this.currentThreadId = threadId;
return Object.keys(innerConfig).length > 0 ? innerConfig : null;
}
private async buildUserInput(prompt: AgentPromptInput): Promise<unknown[]> {
@@ -1,5 +1,5 @@
const TOOL_TOKEN_REGEX = /[a-z0-9]+/g;
const NAMESPACE_SEPARATOR_REGEX = /[.:/]|__/;
const STANDARD_NAMESPACE_SEPARATOR_REGEX = /[.:/]/;
export function normalizeToolName(name: string): string {
return name.trim().toLowerCase();
@@ -20,7 +20,23 @@ export function isSpeakToolName(name: string): boolean {
}
export function isLikelyNamespacedToolName(name: string): boolean {
return NAMESPACE_SEPARATOR_REGEX.test(normalizeToolName(name));
const normalized = normalizeToolName(name);
if (STANDARD_NAMESPACE_SEPARATOR_REGEX.test(normalized)) {
return true;
}
if (!normalized.includes("__")) {
return false;
}
// Keep `__` handling strict to avoid false positives on arbitrary custom names.
const segments = normalized.split("__").filter((segment) => segment.length > 0);
if (segments.length >= 3) {
return true;
}
if (segments.length === 2 && segments[1]!.includes("_")) {
return true;
}
return false;
}
export function isLikelyExternalToolName(name: string): boolean {
@@ -57,7 +57,7 @@ export function isVoicePermissionAllowed(request: AgentPermissionRequest): boole
return false;
}
const mentionsAllowedTooling = containsAny(metadataText, ALLOWED_TOKEN_SET);
const mentionsAllowedTooling = containsAny(metadataText, SPEAK_TOKEN_SET);
const mentionsDeniedTooling = containsAny(metadataText, DENIED_TOKEN_SET);
return mentionsAllowedTooling && !mentionsDeniedTooling;
}
@@ -2,6 +2,7 @@ import { describe, expect, test } from "vitest";
import {
stripShellWrapperPrefix,
extractPrincipalParam,
normalizeToolDisplayName,
stripCwdPrefix,
} from "./tool-call-parsers.js";
@@ -90,6 +91,26 @@ describe("extractPrincipalParam", () => {
});
});
describe("normalizeToolDisplayName", () => {
test("normalizes plain speak name", () => {
expect(normalizeToolDisplayName("speak")).toBe("Speak");
});
test("normalizes codex namespaced speak name", () => {
expect(normalizeToolDisplayName("paseo_voice.speak")).toBe("Speak");
});
test("normalizes claude mcp speak name", () => {
expect(normalizeToolDisplayName("mcp__paseo_voice__speak")).toBe("Speak");
});
test("keeps non-speak names unchanged", () => {
expect(normalizeToolDisplayName("paseo__create_agent")).toBe(
"paseo__create_agent"
);
});
});
describe("stripCwdPrefix", () => {
test("strips cwd prefix from file path", () => {
expect(stripCwdPrefix("/Users/dev/project/src/file.ts", "/Users/dev/project")).toBe("src/file.ts");
@@ -63,6 +63,21 @@ export function stripCwdPrefix(filePath: string, cwd?: string): string {
// This is used for display purposes to show the actual command being run.
const SHELL_WRAPPER_PREFIX_PATTERN = /^\/bin\/(?:zsh|bash|sh)\s+(?:-[a-zA-Z]+\s+)?/;
const CD_AND_PATTERN = /^cd\s+(?:"[^"]+"|'[^']+'|\S+)\s+&&\s+/;
const TOOL_TOKEN_REGEX = /[a-z0-9]+/g;
export function normalizeToolDisplayName(toolName: string): string {
const normalized = toolName.trim().toLowerCase();
if (!normalized) {
return toolName;
}
const tokens = normalized.match(TOOL_TOKEN_REGEX) ?? [];
const leaf = tokens[tokens.length - 1];
if (leaf === "speak") {
return "Speak";
}
return toolName;
}
export function stripShellWrapperPrefix(command: string): string {
const prefixMatch = command.match(SHELL_WRAPPER_PREFIX_PATTERN);