Fix slash commands in imported session previews (#1502)
This commit is contained in:
@@ -16,6 +16,20 @@ describe("extractUserMessageText", () => {
|
||||
expect(extractUserMessageText(content)).toBe("First line\n\nSecond line");
|
||||
});
|
||||
|
||||
test("returns Claude slash command prompts without transcript tags", () => {
|
||||
const content =
|
||||
"<command-message>diagnose</command-message>\n<command-name>/diagnose</command-name>\n<command-args>recently the PR data does not update</command-args>";
|
||||
|
||||
expect(extractUserMessageText(content)).toBe("/diagnose recently the PR data does not update");
|
||||
});
|
||||
|
||||
test("returns the Claude slash command prompt when no args were recorded", () => {
|
||||
const content =
|
||||
"<command-message>caveman:caveman</command-message>\n<command-name>/caveman:caveman</command-name>";
|
||||
|
||||
expect(extractUserMessageText(content)).toBe("/caveman:caveman");
|
||||
});
|
||||
|
||||
test("returns null when no textual content is present", () => {
|
||||
const content = [
|
||||
{ type: "image", source: "foo.png" },
|
||||
|
||||
@@ -860,6 +860,98 @@ describe("normalizeClaudeAskUserQuestionUpdatedInput", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ClaudeAgentClient.listImportableSessions", () => {
|
||||
test("shows Claude slash command prompts without transcript tags", async () => {
|
||||
const tmpConfigDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-import-"));
|
||||
const previousConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
||||
process.env.CLAUDE_CONFIG_DIR = tmpConfigDir;
|
||||
|
||||
try {
|
||||
const commandSessionId = "session-command-import";
|
||||
const argsSessionId = "session-command-args-import";
|
||||
const cwd = "/tmp/paseo-test-claude-import";
|
||||
const sanitized = cwd.replace(/[\\/._:]/g, "-");
|
||||
const projectDir = path.join(tmpConfigDir, "projects", sanitized);
|
||||
await fs.mkdir(projectDir, { recursive: true });
|
||||
const commandSessionFile = path.join(projectDir, `${commandSessionId}.jsonl`);
|
||||
const argsSessionFile = path.join(projectDir, `${argsSessionId}.jsonl`);
|
||||
await fs.writeFile(
|
||||
commandSessionFile,
|
||||
`${JSON.stringify({
|
||||
parentUuid: null,
|
||||
isSidechain: false,
|
||||
type: "user",
|
||||
message: {
|
||||
role: "user",
|
||||
content:
|
||||
"<command-message>caveman:caveman</command-message>\n<command-name>/caveman:caveman</command-name>",
|
||||
},
|
||||
cwd,
|
||||
sessionId: commandSessionId,
|
||||
})}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
argsSessionFile,
|
||||
`${JSON.stringify({
|
||||
parentUuid: null,
|
||||
isSidechain: false,
|
||||
type: "user",
|
||||
message: {
|
||||
role: "user",
|
||||
content:
|
||||
"<command-message>diagnose</command-message>\n<command-name>/diagnose</command-name>\n<command-args>recently the PR data does not update</command-args>",
|
||||
},
|
||||
cwd,
|
||||
sessionId: argsSessionId,
|
||||
})}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
await fs.utimes(
|
||||
commandSessionFile,
|
||||
new Date("2026-06-12T10:00:00.000Z"),
|
||||
new Date("2026-06-12T10:00:00.000Z"),
|
||||
);
|
||||
await fs.utimes(
|
||||
argsSessionFile,
|
||||
new Date("2026-06-12T11:00:00.000Z"),
|
||||
new Date("2026-06-12T11:00:00.000Z"),
|
||||
);
|
||||
|
||||
const client = new ClaudeAgentClient({
|
||||
logger: createTestLogger(),
|
||||
resolveBinary: async () => "/test/claude/bin",
|
||||
});
|
||||
|
||||
await expect(client.listImportableSessions({ limit: 2 })).resolves.toEqual([
|
||||
{
|
||||
providerHandleId: argsSessionId,
|
||||
cwd,
|
||||
title: "/diagnose recently the PR data does not update",
|
||||
firstPromptPreview: "/diagnose recently the PR data does not update",
|
||||
lastPromptPreview: "/diagnose recently the PR data does not update",
|
||||
lastActivityAt: new Date("2026-06-12T11:00:00.000Z"),
|
||||
},
|
||||
{
|
||||
providerHandleId: commandSessionId,
|
||||
cwd,
|
||||
title: "/caveman:caveman",
|
||||
firstPromptPreview: "/caveman:caveman",
|
||||
lastPromptPreview: "/caveman:caveman",
|
||||
lastActivityAt: new Date("2026-06-12T10:00:00.000Z"),
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
if (previousConfigDir === undefined) {
|
||||
delete process.env.CLAUDE_CONFIG_DIR;
|
||||
} else {
|
||||
process.env.CLAUDE_CONFIG_DIR = previousConfigDir;
|
||||
}
|
||||
await fs.rm(tmpConfigDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("ClaudeAgentSession context window usage", () => {
|
||||
const logger = createTestLogger();
|
||||
|
||||
|
||||
@@ -604,6 +604,9 @@ function isClaudeNoResponsePlaceholderText(value: unknown): boolean {
|
||||
|
||||
const LOCAL_COMMAND_STDOUT_PATTERN =
|
||||
/^\s*<local-command-stdout>[\s\S]*<\/local-command-stdout>\s*$/;
|
||||
const CLAUDE_COMMAND_MESSAGE_PATTERN = /<command-message>([\s\S]*?)<\/command-message>/;
|
||||
const CLAUDE_COMMAND_ARGS_PATTERN = /<command-args>([\s\S]*?)<\/command-args>/;
|
||||
const CLAUDE_COMMAND_NAME_PATTERN = /<command-name>([\s\S]*?)<\/command-name>/;
|
||||
|
||||
function isClaudeLocalCommandStdout(value: unknown): boolean {
|
||||
const normalized = normalizeClaudeTranscriptText(value);
|
||||
@@ -659,7 +662,7 @@ export function extractUserMessageText(content: unknown): string | null {
|
||||
if (!normalized || isClaudeTranscriptNoiseText(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
return normalizeClaudeUserPromptText(normalized);
|
||||
}
|
||||
|
||||
if (!Array.isArray(content)) {
|
||||
@@ -675,7 +678,10 @@ export function extractUserMessageText(content: unknown): string | null {
|
||||
if (text && text.trim()) {
|
||||
const trimmed = text.trim();
|
||||
if (!isClaudeTranscriptNoiseText(trimmed)) {
|
||||
parts.push(trimmed);
|
||||
const normalized = normalizeClaudeUserPromptText(trimmed);
|
||||
if (normalized) {
|
||||
parts.push(normalized);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -683,7 +689,10 @@ export function extractUserMessageText(content: unknown): string | null {
|
||||
if (input && input.trim()) {
|
||||
const trimmed = input.trim();
|
||||
if (!isClaudeTranscriptNoiseText(trimmed)) {
|
||||
parts.push(trimmed);
|
||||
const normalized = normalizeClaudeUserPromptText(trimmed);
|
||||
if (normalized) {
|
||||
parts.push(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5082,6 +5091,38 @@ function normalizeImportablePromptPreview(text: string): string | null {
|
||||
return normalized.length > 160 ? normalized.slice(0, 160) : normalized;
|
||||
}
|
||||
|
||||
function normalizeClaudeUserPromptText(text: string): string | null {
|
||||
const normalized = text.trim();
|
||||
if (!CLAUDE_COMMAND_MESSAGE_PATTERN.test(normalized)) {
|
||||
return normalized || null;
|
||||
}
|
||||
|
||||
const command = readClaudeCommandPromptName(normalized);
|
||||
if (!command) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const commandArgs = normalized.match(CLAUDE_COMMAND_ARGS_PATTERN)?.[1]?.trim();
|
||||
if (commandArgs) {
|
||||
return `${command} ${commandArgs}`;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
function readClaudeCommandPromptName(text: string): string | null {
|
||||
const commandName = text.match(CLAUDE_COMMAND_NAME_PATTERN)?.[1]?.trim();
|
||||
if (commandName) {
|
||||
return commandName.startsWith("/") ? commandName : `/${commandName}`;
|
||||
}
|
||||
|
||||
const commandMessage = text.match(CLAUDE_COMMAND_MESSAGE_PATTERN)?.[1]?.trim();
|
||||
if (!commandMessage) {
|
||||
return null;
|
||||
}
|
||||
return commandMessage.startsWith("/") ? commandMessage : `/${commandMessage}`;
|
||||
}
|
||||
|
||||
function extractClaudeUserText(messageRaw: unknown): string | null {
|
||||
const message = toObjectRecord(messageRaw);
|
||||
if (!message) {
|
||||
@@ -5089,11 +5130,13 @@ function extractClaudeUserText(messageRaw: unknown): string | null {
|
||||
}
|
||||
if (typeof message.content === "string") {
|
||||
const normalized = message.content.trim();
|
||||
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
||||
if (!normalized || isClaudeTranscriptNoiseText(normalized)) return null;
|
||||
return normalizeClaudeUserPromptText(normalized);
|
||||
}
|
||||
if (typeof message.text === "string") {
|
||||
const normalized = message.text.trim();
|
||||
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
||||
if (!normalized || isClaudeTranscriptNoiseText(normalized)) return null;
|
||||
return normalizeClaudeUserPromptText(normalized);
|
||||
}
|
||||
if (isUnknownArray(message.content)) {
|
||||
for (const block of message.content) {
|
||||
@@ -5101,7 +5144,7 @@ function extractClaudeUserText(messageRaw: unknown): string | null {
|
||||
if (blockRecord && typeof blockRecord.text === "string") {
|
||||
const normalized = blockRecord.text.trim();
|
||||
if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
|
||||
return normalized;
|
||||
return normalizeClaudeUserPromptText(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user