feat: implement sub chain message view feature

This commit is contained in:
d-kimsuon
2025-08-30 15:00:02 +09:00
parent bfa19a6e85
commit bd0aeb490b
14 changed files with 592 additions and 44 deletions

View File

@@ -20,7 +20,7 @@ export const getProjects = async (): Promise<{ projects: Project[] }> => {
claudeProjectPath: fullPath,
meta: await getProjectMeta(fullPath),
};
})
}),
);
return {

View File

@@ -1,12 +1,13 @@
import { statSync } from "node:fs";
import { readFile } from "node:fs/promises";
import type { Conversation } from "../../../lib/conversation-schema";
import { type ParsedCommand, parseCommandXml } from "../parseCommandXml";
import { parseJsonl } from "../parseJsonl";
import type { SessionMeta } from "../types";
const firstCommandCache = new Map<string, ParsedCommand | null>();
const ignoreCommands = ["/clear", "/login", "/logout", "/exit"];
const getFirstCommand = (
jsonlFilePath: string,
lines: string[],
@@ -16,7 +17,7 @@ const getFirstCommand = (
return cached;
}
let firstUserMessage: Conversation | null = null;
let firstCommand: ParsedCommand | null = null;
for (const line of lines) {
const conversation = parseJsonl(line).at(0);
@@ -25,27 +26,49 @@ const getFirstCommand = (
continue;
}
firstUserMessage = conversation;
const firstUserText =
conversation === null
? null
: typeof conversation.message.content === "string"
? conversation.message.content
: (() => {
const firstContent = conversation.message.content.at(0);
if (firstContent === undefined) return null;
if (typeof firstContent === "string") return firstContent;
if (firstContent.type === "text") return firstContent.text;
return null;
})();
if (firstUserText === null) {
continue;
}
if (
firstUserText ===
"Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to."
) {
continue;
}
const command = parseCommandXml(firstUserText);
if (
command.kind === "local-command-1" ||
command.kind === "local-command-2"
) {
continue;
}
if (
command.kind === "command" &&
ignoreCommands.includes(command.commandName)
) {
continue;
}
firstCommand = command;
break;
}
const firstMessageText =
firstUserMessage === null
? null
: typeof firstUserMessage.message.content === "string"
? firstUserMessage.message.content
: (() => {
const firstContent = firstUserMessage.message.content.at(0);
if (firstContent === undefined) return null;
if (typeof firstContent === "string") return firstContent;
if (firstContent.type === "text") return firstContent.text;
return null;
})();
const firstCommand =
firstMessageText === null ? null : parseCommandXml(firstMessageText);
if (firstCommand !== null) {
firstCommandCache.set(jsonlFilePath, firstCommand);
}

View File

@@ -6,7 +6,7 @@ import type { Session } from "../types";
import { getSessionMeta } from "./getSessionMeta";
export const getSessions = async (
projectId: string
projectId: string,
): Promise<{ sessions: Session[] }> => {
const claudeProjectPath = decodeProjectId(projectId);
@@ -22,7 +22,7 @@ export const getSessions = async (
jsonlFilePath: fullPath,
meta: await getSessionMeta(fullPath),
};
})
}),
);
return {