Update files
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user