Update files
This commit is contained in:
+6
-4
@@ -23,12 +23,14 @@
|
|||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"start": "npm run start --workspace=@getpaseo/server",
|
"start": "npm run start --workspace=@getpaseo/server",
|
||||||
"android": "npm run android --workspace=@getpaseo/app",
|
"android": "npm run android --workspace=@getpaseo/app",
|
||||||
"android:release": "ANDROID_VARIANT=productionRelease npm run android --workspace=@getpaseo/app",
|
"android:fast": "npm run android:fast --workspace=@getpaseo/app",
|
||||||
"android:dev-release": "ANDROID_VARIANT=devRelease npm run android --workspace=@getpaseo/app",
|
"android:debug": "npm run android:debug --workspace=@getpaseo/app",
|
||||||
|
"android:prod": "npm run android:prod --workspace=@getpaseo/app",
|
||||||
|
"android:release": "npm run android:prod --workspace=@getpaseo/app",
|
||||||
"ios": "npm run ios --workspace=@getpaseo/app",
|
"ios": "npm run ios --workspace=@getpaseo/app",
|
||||||
"web": "npm run web --workspace=@getpaseo/app",
|
"web": "npm run web --workspace=@getpaseo/app",
|
||||||
"desktop": "npm run dev --workspace=@getpaseo/desktop",
|
"dev:desktop": "npm run dev --workspace=@getpaseo/desktop",
|
||||||
"desktop:build": "npm run build --workspace=@getpaseo/desktop",
|
"build:desktop": "npm run build --workspace=@getpaseo/desktop",
|
||||||
"cli": "npx tsx packages/cli/src/index.js"
|
"cli": "npx tsx packages/cli/src/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
// App name and package ID are controlled by Gradle product flavors (dev/production)
|
|
||||||
// See plugins/with-android-product-flavors.js for flavor configuration
|
|
||||||
|
|
||||||
const pkg = require("./package.json");
|
const pkg = require("./package.json");
|
||||||
|
const appVariant = process.env.APP_VARIANT ?? "prod";
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
prod: {
|
||||||
|
name: "Paseo",
|
||||||
|
packageId: "sh.paseo",
|
||||||
|
},
|
||||||
|
debug: {
|
||||||
|
name: "Paseo Debug",
|
||||||
|
packageId: "sh.paseo.debug",
|
||||||
|
},
|
||||||
|
fast: {
|
||||||
|
name: "Paseo Dev",
|
||||||
|
packageId: "sh.paseo.dev",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const variant = variants[appVariant] ?? variants.prod;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
expo: {
|
expo: {
|
||||||
name: "Paseo",
|
name: variant.name,
|
||||||
slug: "voice-mobile",
|
slug: "voice-mobile",
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
orientation: "portrait",
|
orientation: "portrait",
|
||||||
@@ -26,7 +41,7 @@ export default {
|
|||||||
"This app needs access to the microphone for voice commands.",
|
"This app needs access to the microphone for voice commands.",
|
||||||
ITSAppUsesNonExemptEncryption: false,
|
ITSAppUsesNonExemptEncryption: false,
|
||||||
},
|
},
|
||||||
bundleIdentifier: "sh.paseo",
|
bundleIdentifier: variant.packageId,
|
||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
adaptiveIcon: {
|
adaptiveIcon: {
|
||||||
@@ -45,8 +60,7 @@ export default {
|
|||||||
"CAMERA",
|
"CAMERA",
|
||||||
"android.permission.CAMERA",
|
"android.permission.CAMERA",
|
||||||
],
|
],
|
||||||
// Base package - Gradle product flavors override this per variant
|
package: variant.packageId,
|
||||||
package: "sh.paseo",
|
|
||||||
},
|
},
|
||||||
web: {
|
web: {
|
||||||
output: "single",
|
output: "single",
|
||||||
@@ -91,7 +105,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"./plugins/with-android-product-flavors",
|
|
||||||
],
|
],
|
||||||
experiments: {
|
experiments: {
|
||||||
typedRoutes: true,
|
typedRoutes: true,
|
||||||
|
|||||||
+23
-7
@@ -3,20 +3,36 @@
|
|||||||
"version": ">= 14.0.0"
|
"version": ">= 14.0.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"development": {
|
"debug": {
|
||||||
"developmentClient": true,
|
"developmentClient": true,
|
||||||
"distribution": "internal",
|
"distribution": "internal",
|
||||||
"channel": "development"
|
"channel": "debug",
|
||||||
|
"env": {
|
||||||
|
"APP_VARIANT": "debug"
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"gradleCommand": ":app:assembleDebug"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"preview": {
|
"fast": {
|
||||||
|
"developmentClient": true,
|
||||||
"distribution": "internal",
|
"distribution": "internal",
|
||||||
"channel": "preview"
|
"channel": "fast",
|
||||||
|
"env": {
|
||||||
|
"APP_VARIANT": "fast"
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"gradleCommand": ":app:assembleDebugOptimized"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"prod": {
|
||||||
"channel": "production"
|
"channel": "production",
|
||||||
|
"env": {
|
||||||
|
"APP_VARIANT": "prod"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"submit": {
|
"submit": {
|
||||||
"production": {}
|
"prod": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
"android": "ANDROID_VARIANT=${ANDROID_VARIANT:-devDebug} expo run:android --variant=${ANDROID_VARIANT:-devDebug}",
|
"android": "npm run android:fast",
|
||||||
"android:release": "ANDROID_VARIANT=${ANDROID_VARIANT:-productionRelease} expo run:android --variant=${ANDROID_VARIANT:-productionRelease}",
|
"android:fast": "APP_VARIANT=fast expo run:android --variant=debugOptimized",
|
||||||
|
"android:debug": "APP_VARIANT=debug expo run:android --variant=debug",
|
||||||
|
"android:prod": "APP_VARIANT=prod expo run:android --variant=release",
|
||||||
|
"android:release": "npm run android:prod",
|
||||||
"ios": "expo run:ios",
|
"ios": "expo run:ios",
|
||||||
"ios:release": "expo run:ios --configuration Release",
|
"ios:release": "expo run:ios --configuration Release",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
const { withAppBuildGradle } = require("expo/config-plugins");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expo config plugin to add Android product flavors (dev/production)
|
|
||||||
* This allows building both dev and production variants without re-running prebuild
|
|
||||||
*
|
|
||||||
* Variants created:
|
|
||||||
* - devDebug: Development build with dev tools
|
|
||||||
* - devRelease: Development build optimized
|
|
||||||
* - productionDebug: Production build with dev tools
|
|
||||||
* - productionRelease: Production build optimized (for app store)
|
|
||||||
*/
|
|
||||||
function withAndroidProductFlavors(config) {
|
|
||||||
return withAppBuildGradle(config, (config) => {
|
|
||||||
let buildGradle = config.modResults.contents;
|
|
||||||
|
|
||||||
// Add debuggableVariants to the react block
|
|
||||||
buildGradle = buildGradle.replace(
|
|
||||||
'// debuggableVariants = ["liteDebug", "prodDebug"]',
|
|
||||||
'debuggableVariants = ["devDebug", "productionDebug"]'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add product flavors after signingConfigs block
|
|
||||||
const signingConfigsEnd = buildGradle.indexOf(
|
|
||||||
" }\n buildTypes {",
|
|
||||||
buildGradle.indexOf("signingConfigs {")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (signingConfigsEnd === -1) {
|
|
||||||
console.warn(
|
|
||||||
"Could not find signingConfigs end in build.gradle, skipping product flavors"
|
|
||||||
);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
const productFlavorsBlock = ` }
|
|
||||||
flavorDimensions "environment"
|
|
||||||
productFlavors {
|
|
||||||
dev {
|
|
||||||
dimension "environment"
|
|
||||||
applicationId "sh.paseo.dev"
|
|
||||||
resValue "string", "app_name", "Paseo (Dev)"
|
|
||||||
}
|
|
||||||
production {
|
|
||||||
dimension "environment"
|
|
||||||
applicationId "sh.paseo"
|
|
||||||
resValue "string", "app_name", "Paseo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildTypes {`;
|
|
||||||
|
|
||||||
buildGradle = buildGradle.replace(
|
|
||||||
" }\n buildTypes {",
|
|
||||||
productFlavorsBlock
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove applicationId from defaultConfig since flavors handle it
|
|
||||||
buildGradle = buildGradle.replace(
|
|
||||||
/applicationId ['"]com\.moboudra\.paseo['"]\n\s*/,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
config.modResults.contents = buildGradle;
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = withAndroidProductFlavors;
|
|
||||||
@@ -357,7 +357,7 @@ describe("curateAgentActivity", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const result = curateAgentActivity(timeline);
|
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", () => {
|
test("shows MCP tool input JSON", () => {
|
||||||
@@ -389,7 +389,22 @@ describe("curateAgentActivity", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const result = curateAgentActivity(timeline);
|
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 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";
|
import { isLikelyExternalToolName } from "./tool-name-normalization.js";
|
||||||
|
|
||||||
const DEFAULT_MAX_ITEMS = 40;
|
const DEFAULT_MAX_ITEMS = 40;
|
||||||
@@ -147,16 +147,17 @@ export function curateAgentActivity(
|
|||||||
break;
|
break;
|
||||||
case "tool_call": {
|
case "tool_call": {
|
||||||
flushBuffers(lines, buffers);
|
flushBuffers(lines, buffers);
|
||||||
|
const displayName = normalizeToolDisplayName(item.name);
|
||||||
const inputJson = formatToolInputJson(item.input);
|
const inputJson = formatToolInputJson(item.input);
|
||||||
if (isLikelyExternalToolName(item.name) && inputJson) {
|
if (isLikelyExternalToolName(item.name) && inputJson) {
|
||||||
lines.push(`[${item.name}] ${inputJson}`);
|
lines.push(`[${displayName}] ${inputJson}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const principal = extractPrincipalParam(item.input);
|
const principal = extractPrincipalParam(item.input);
|
||||||
if (principal) {
|
if (principal) {
|
||||||
lines.push(`[${item.name}] ${principal}`);
|
lines.push(`[${displayName}] ${principal}`);
|
||||||
} else {
|
} else {
|
||||||
lines.push(`[${item.name}]`);
|
lines.push(`[${displayName}]`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1350,7 +1350,15 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
if (ids.includes(this.currentThreadId)) {
|
if (ids.includes(this.currentThreadId)) {
|
||||||
return;
|
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) {
|
} catch (error) {
|
||||||
this.logger.warn({ error }, "Failed to resume Codex thread, starting new thread");
|
this.logger.warn({ error }, "Failed to resume Codex thread, starting new thread");
|
||||||
this.currentThreadId = null;
|
this.currentThreadId = null;
|
||||||
@@ -1438,6 +1446,13 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
if (options?.outputSchema) {
|
if (options?.outputSchema) {
|
||||||
params.outputSchema = 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);
|
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 preset = MODE_PRESETS[this.currentMode] ?? MODE_PRESETS[DEFAULT_CODEX_MODE_ID];
|
||||||
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
||||||
const sandbox = this.config.sandboxMode ?? preset.sandbox;
|
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> = {};
|
const innerConfig: Record<string, unknown> = {};
|
||||||
if (this.config.mcpServers) {
|
if (this.config.mcpServers) {
|
||||||
const mcpServers: Record<string, CodexMcpServerConfig> = {};
|
const mcpServers: Record<string, CodexMcpServerConfig> = {};
|
||||||
@@ -1718,21 +1752,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
if (this.config.extra?.codex) {
|
if (this.config.extra?.codex) {
|
||||||
Object.assign(innerConfig, this.config.extra.codex);
|
Object.assign(innerConfig, this.config.extra.codex);
|
||||||
}
|
}
|
||||||
const response = (await this.client.request("thread/start", {
|
return Object.keys(innerConfig).length > 0 ? innerConfig : null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildUserInput(prompt: AgentPromptInput): Promise<unknown[]> {
|
private async buildUserInput(prompt: AgentPromptInput): Promise<unknown[]> {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const TOOL_TOKEN_REGEX = /[a-z0-9]+/g;
|
const TOOL_TOKEN_REGEX = /[a-z0-9]+/g;
|
||||||
const NAMESPACE_SEPARATOR_REGEX = /[.:/]|__/;
|
const STANDARD_NAMESPACE_SEPARATOR_REGEX = /[.:/]/;
|
||||||
|
|
||||||
export function normalizeToolName(name: string): string {
|
export function normalizeToolName(name: string): string {
|
||||||
return name.trim().toLowerCase();
|
return name.trim().toLowerCase();
|
||||||
@@ -20,7 +20,23 @@ export function isSpeakToolName(name: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isLikelyNamespacedToolName(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 {
|
export function isLikelyExternalToolName(name: string): boolean {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function isVoicePermissionAllowed(request: AgentPermissionRequest): boole
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mentionsAllowedTooling = containsAny(metadataText, ALLOWED_TOKEN_SET);
|
const mentionsAllowedTooling = containsAny(metadataText, SPEAK_TOKEN_SET);
|
||||||
const mentionsDeniedTooling = containsAny(metadataText, DENIED_TOKEN_SET);
|
const mentionsDeniedTooling = containsAny(metadataText, DENIED_TOKEN_SET);
|
||||||
return mentionsAllowedTooling && !mentionsDeniedTooling;
|
return mentionsAllowedTooling && !mentionsDeniedTooling;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from "vitest";
|
|||||||
import {
|
import {
|
||||||
stripShellWrapperPrefix,
|
stripShellWrapperPrefix,
|
||||||
extractPrincipalParam,
|
extractPrincipalParam,
|
||||||
|
normalizeToolDisplayName,
|
||||||
stripCwdPrefix,
|
stripCwdPrefix,
|
||||||
} from "./tool-call-parsers.js";
|
} 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", () => {
|
describe("stripCwdPrefix", () => {
|
||||||
test("strips cwd prefix from file path", () => {
|
test("strips cwd prefix from file path", () => {
|
||||||
expect(stripCwdPrefix("/Users/dev/project/src/file.ts", "/Users/dev/project")).toBe("src/file.ts");
|
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.
|
// 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 SHELL_WRAPPER_PREFIX_PATTERN = /^\/bin\/(?:zsh|bash|sh)\s+(?:-[a-zA-Z]+\s+)?/;
|
||||||
const CD_AND_PATTERN = /^cd\s+(?:"[^"]+"|'[^']+'|\S+)\s+&&\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 {
|
export function stripShellWrapperPrefix(command: string): string {
|
||||||
const prefixMatch = command.match(SHELL_WRAPPER_PREFIX_PATTERN);
|
const prefixMatch = command.match(SHELL_WRAPPER_PREFIX_PATTERN);
|
||||||
|
|||||||
Reference in New Issue
Block a user