Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
698 lines
19 KiB
TypeScript
698 lines
19 KiB
TypeScript
import { formatWorkspaceFilePathForDisplay } from "@tutti-os/workspace-file-manager/services";
|
|
import { fileChangePathsFromChanges } from "./workspaceAgentFileChangePayload.ts";
|
|
import type {
|
|
AgentActivityMessage,
|
|
AgentActivitySession
|
|
} from "@tutti-os/agent-activity-core";
|
|
import type {
|
|
CollectWorkspaceAgentGeneratedFilesOptions,
|
|
WorkspaceAgentChangedFile
|
|
} from "./workspaceAgentActivityListTypes";
|
|
import { workspaceAgentSessionMessageAliases } from "./workspaceAgentSessionMessageAliases.ts";
|
|
import { isImageGenerationToolCall } from "./imageGenerationTool.ts";
|
|
import {
|
|
isInsideOrEqualWorkspaceFilePath,
|
|
normalizeWorkspaceFilePath
|
|
} from "../actions/workspaceFilePathCandidate";
|
|
|
|
export interface WorkspaceAgentGeneratedFilesSource {
|
|
sessionMessagesById: Readonly<Record<string, AgentActivityMessage[]>>;
|
|
sessions: readonly AgentActivitySession[];
|
|
}
|
|
|
|
export function collectWorkspaceAgentGeneratedFiles(
|
|
source: WorkspaceAgentGeneratedFilesSource,
|
|
options: CollectWorkspaceAgentGeneratedFilesOptions = {}
|
|
): WorkspaceAgentChangedFile[] {
|
|
const requestedWorkspaceRoot = normalizeComparablePath(
|
|
options.workspaceRoot ?? ""
|
|
);
|
|
const sessionCwdFilter = normalizeComparablePath(
|
|
options.sessionCwd ?? "",
|
|
requestedWorkspaceRoot
|
|
);
|
|
const allowedAgentTargetIds = options.agentTargetIds
|
|
? new Set(options.agentTargetIds.map((id) => id.trim()).filter(Boolean))
|
|
: null;
|
|
const provenanceSessions = allowedAgentTargetIds
|
|
? source.sessions.filter(
|
|
(session) =>
|
|
session.agentTargetId !== null &&
|
|
allowedAgentTargetIds.has(session.agentTargetId)
|
|
)
|
|
: source.sessions;
|
|
const workspaceRoot =
|
|
sessionCwdFilter ||
|
|
requestedWorkspaceRoot ||
|
|
resolveWorkspaceRootFromSessions(provenanceSessions);
|
|
const sessions = sessionCwdFilter
|
|
? provenanceSessions.filter(
|
|
(session) =>
|
|
normalizeComparablePath(session.cwd ?? "", workspaceRoot) ===
|
|
sessionCwdFilter
|
|
)
|
|
: provenanceSessions;
|
|
const filesByPath = new Map<string, WorkspaceAgentChangedFile>();
|
|
|
|
for (const session of sessions) {
|
|
const sessionCwd =
|
|
normalizeComparablePath(session.cwd ?? "", workspaceRoot) ||
|
|
workspaceRoot;
|
|
const normalizePath = createAgentGeneratedFilePathNormalizer({
|
|
sessionCwd,
|
|
workspaceRoot
|
|
});
|
|
const messages = resolveWorkspaceAgentSessionMessages(
|
|
source.sessionMessagesById,
|
|
session
|
|
);
|
|
if (messages.length === 0) {
|
|
continue;
|
|
}
|
|
for (const file of changedFilesForSession(messages, normalizePath, {
|
|
requireSuccessfulFileChangeTool: true
|
|
})) {
|
|
filesByPath.set(file.path, file);
|
|
}
|
|
for (const path of imageGenerationPathsFromMessages(
|
|
messages,
|
|
normalizePath
|
|
)) {
|
|
if (filesByPath.has(path)) {
|
|
continue;
|
|
}
|
|
filesByPath.set(path, {
|
|
path,
|
|
label: path.split("/").filter(Boolean).at(-1) ?? path
|
|
});
|
|
}
|
|
}
|
|
|
|
return applyShortestUniqueFileLabels([...filesByPath.values()]);
|
|
}
|
|
|
|
type ChangedFilePathNormalizer = (value: unknown) => string | null;
|
|
|
|
interface ChangedFileCollectionOptions {
|
|
canonicalFileChanges?: readonly Record<string, unknown>[];
|
|
requireSuccessfulFileChangeTool?: boolean;
|
|
}
|
|
|
|
export function changedFilesForSession(
|
|
messages: readonly AgentActivityMessage[],
|
|
normalizePath: ChangedFilePathNormalizer = defaultChangedFilePathNormalizer,
|
|
options: ChangedFileCollectionOptions = {}
|
|
): WorkspaceAgentChangedFile[] {
|
|
const changedFilesByPath = new Map<string, WorkspaceAgentChangedFile>();
|
|
const appendPath = (path: string | null): void => {
|
|
if (!path || changedFilesByPath.has(path)) {
|
|
return;
|
|
}
|
|
changedFilesByPath.set(path, {
|
|
path,
|
|
label: path
|
|
});
|
|
};
|
|
|
|
for (const fileChanges of options.canonicalFileChanges ?? []) {
|
|
for (const path of canonicalFileChangePaths(fileChanges, normalizePath)) {
|
|
appendPath(path);
|
|
}
|
|
}
|
|
|
|
for (const message of messages) {
|
|
for (const path of changedFilePathsFromMessage(
|
|
message,
|
|
normalizePath,
|
|
options
|
|
)) {
|
|
appendPath(path);
|
|
}
|
|
}
|
|
|
|
return applyShortestUniqueFileLabels(Array.from(changedFilesByPath.values()));
|
|
}
|
|
|
|
function canonicalFileChangePaths(
|
|
fileChanges: Record<string, unknown>,
|
|
normalizePath: ChangedFilePathNormalizer
|
|
): string[] {
|
|
const files = arrayValue(fileChanges.files);
|
|
if ("files" in fileChanges) {
|
|
return (files ?? [])
|
|
.map((file) =>
|
|
normalizeCanonicalFileChangePath(objectValue(file)?.path, normalizePath)
|
|
)
|
|
.filter((path): path is string => path !== null);
|
|
}
|
|
|
|
const paths =
|
|
"changes" in fileChanges
|
|
? fileChangePathsFromChanges(fileChanges.changes)
|
|
: "coverage" in fileChanges
|
|
? []
|
|
: fileChangePathsFromChanges(fileChanges);
|
|
return paths
|
|
.map((path) => normalizeCanonicalFileChangePath(path, normalizePath))
|
|
.filter((path): path is string => path !== null);
|
|
}
|
|
|
|
function normalizeCanonicalFileChangePath(
|
|
value: unknown,
|
|
normalizePath: ChangedFilePathNormalizer
|
|
): string | null {
|
|
return (
|
|
normalizePath(value) ??
|
|
normalizedChangedFilePath(value, { allowRelative: true })
|
|
);
|
|
}
|
|
|
|
function changedFilePathsFromMessage(
|
|
message: AgentActivityMessage,
|
|
normalizePath: ChangedFilePathNormalizer = defaultChangedFilePathNormalizer,
|
|
options: ChangedFileCollectionOptions = {}
|
|
): string[] {
|
|
const isSuccessfulFileChangeTool = isSuccessfulFileChangeToolMessage(message);
|
|
if (options.requireSuccessfulFileChangeTool && !isSuccessfulFileChangeTool) {
|
|
return [];
|
|
}
|
|
const payload = objectValue(message.payload);
|
|
const explicitFileChanges = fileChangePaths(
|
|
arrayValue(objectValue(payload?.fileChanges)?.files),
|
|
normalizePath
|
|
);
|
|
if (explicitFileChanges.length > 0) {
|
|
return explicitFileChanges;
|
|
}
|
|
if (!isSuccessfulFileChangeTool) {
|
|
return [];
|
|
}
|
|
|
|
const input = objectValue(payload?.input);
|
|
const output = objectValue(payload?.output);
|
|
const paths = dedupeStrings([
|
|
...pathsValue(payload?.paths, normalizePath),
|
|
...pathsValue(output?.paths, normalizePath),
|
|
...pathsValue(input?.paths, normalizePath),
|
|
...changeMapPaths(payload?.changes, normalizePath),
|
|
...changeMapPaths(output?.changes, normalizePath),
|
|
...changeMapPaths(input?.changes, normalizePath),
|
|
stringValue(payload?.path),
|
|
stringValue(payload?.filePath),
|
|
stringValue(payload?.file_path),
|
|
stringValue(input?.path),
|
|
stringValue(input?.filePath),
|
|
stringValue(input?.file_path),
|
|
stringValue(output?.path),
|
|
stringValue(output?.filePath),
|
|
stringValue(output?.file_path)
|
|
]);
|
|
return paths
|
|
.map((path) => normalizePath(path))
|
|
.filter((path): path is string => path !== null);
|
|
}
|
|
|
|
function defaultChangedFilePathNormalizer(value: unknown): string | null {
|
|
return normalizedChangedFilePath(value);
|
|
}
|
|
|
|
function resolveWorkspaceRootFromSessions(
|
|
sessions: readonly AgentActivitySession[]
|
|
): string {
|
|
for (const session of sessions) {
|
|
const cwd = normalizeComparablePath(session.cwd ?? "");
|
|
if (cwd) {
|
|
return cwd;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function normalizeComparablePath(path: string, rootPath?: string): string {
|
|
const trimmed = path.trim();
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
const normalizedInput = trimmed.replaceAll("\\", "/");
|
|
const normalized = normalizeWorkspaceFilePath(
|
|
trimmed,
|
|
rootPath?.trim().replaceAll("\\", "/")
|
|
);
|
|
if (
|
|
/^\/[A-Za-z]:\//.test(normalized) &&
|
|
!/^[A-Za-z]:\//.test(normalizedInput)
|
|
) {
|
|
return formatWorkspaceFilePathForDisplay(normalized, "win32")
|
|
.replaceAll("\\", "/")
|
|
.replace(/\/+$/, "");
|
|
}
|
|
return normalizedInput.replace(/\/+$/, "");
|
|
}
|
|
|
|
function createAgentGeneratedFilePathNormalizer(input: {
|
|
sessionCwd?: string | null;
|
|
workspaceRoot?: string | null;
|
|
}): ChangedFilePathNormalizer {
|
|
const workspaceRoot = normalizeComparablePath(input.workspaceRoot ?? "");
|
|
const sessionCwd =
|
|
normalizeComparablePath(input.sessionCwd ?? "", workspaceRoot) ||
|
|
workspaceRoot;
|
|
return (value: unknown) => {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
return resolveAgentGeneratedFilePath(value, workspaceRoot, sessionCwd);
|
|
};
|
|
}
|
|
|
|
function resolveAgentGeneratedFilePath(
|
|
rawPath: string,
|
|
workspaceRoot: string,
|
|
sessionCwd: string
|
|
): string | null {
|
|
// Agent activity payloads cross the daemon boundary using the host's native
|
|
// separator. Normalize before checking virtual namespaces such as the
|
|
// out-of-workspace generated-image directory; otherwise a Windows path is
|
|
// rejected because its `\\` segments are invisible to the `/`-only check.
|
|
const path = normalizeComparablePath(rawPath, workspaceRoot);
|
|
if (!path || isStructuredPayloadPath(path)) {
|
|
return null;
|
|
}
|
|
if (path.startsWith("/workspace/")) {
|
|
return path;
|
|
}
|
|
if (isAgentStateGeneratedImagePath(path)) {
|
|
return path;
|
|
}
|
|
if (isAbsoluteAgentGeneratedFilePath(path)) {
|
|
if (
|
|
workspaceRoot &&
|
|
!isInsideOrEqualWorkspaceFilePath(path, workspaceRoot) &&
|
|
!isAgentStateGeneratedImagePath(path)
|
|
) {
|
|
return null;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
const base = sessionCwd || workspaceRoot;
|
|
if (!base) {
|
|
return null;
|
|
}
|
|
const resolved = joinAgentGeneratedFilePath(base, path.replace(/^\.?\//, ""));
|
|
if (
|
|
workspaceRoot &&
|
|
!isInsideOrEqualWorkspaceFilePath(resolved, workspaceRoot) &&
|
|
!isAgentStateGeneratedImagePath(resolved)
|
|
) {
|
|
return null;
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
function isAbsoluteAgentGeneratedFilePath(path: string): boolean {
|
|
return path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path);
|
|
}
|
|
|
|
function joinAgentGeneratedFilePath(
|
|
base: string,
|
|
relativePath: string
|
|
): string {
|
|
const normalizedBase = base.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
return `${normalizedBase}/${normalizedRelative}`;
|
|
}
|
|
|
|
function imageGenerationPathsFromMessages(
|
|
messages: readonly AgentActivityMessage[],
|
|
normalizePath: ChangedFilePathNormalizer
|
|
): string[] {
|
|
const paths: string[] = [];
|
|
for (const message of messages) {
|
|
const payload = objectValue(message.payload);
|
|
if (!payload) {
|
|
continue;
|
|
}
|
|
const output = objectValue(payload.output);
|
|
const savedPath = stringValue(output?.savedPath);
|
|
const savedPaths =
|
|
arrayValue(output?.savedPaths)?.flatMap((value) => {
|
|
const path = stringValue(value);
|
|
return path ? [path] : [];
|
|
}) ?? [];
|
|
if (
|
|
!isImageGenerationToolCall({
|
|
toolName: stringValue(payload.toolName),
|
|
displayName: stringValue(payload.name),
|
|
outputSavedPath: savedPath,
|
|
outputSavedPaths: savedPaths,
|
|
outputMimeType: output?.imageMimeType,
|
|
outputText: output?.text,
|
|
inputPrompt: objectValue(payload.input)?.prompt
|
|
})
|
|
) {
|
|
continue;
|
|
}
|
|
for (const uri of dedupeStrings([savedPath, ...savedPaths])) {
|
|
const normalized = normalizePath(uri);
|
|
if (normalized) {
|
|
paths.push(normalized);
|
|
}
|
|
}
|
|
}
|
|
return dedupeStrings(paths);
|
|
}
|
|
|
|
function isAgentStateGeneratedImagePath(path: string): boolean {
|
|
const segments = path.split("/").filter(Boolean);
|
|
const stateRootIndex = segments.findIndex(
|
|
(segment) => segment === ".tutti" || segment === ".tutti-dev"
|
|
);
|
|
if (stateRootIndex < 0) {
|
|
return false;
|
|
}
|
|
const statePath = segments.slice(stateRootIndex);
|
|
return (
|
|
statePath[1] === "agent" &&
|
|
statePath[2] === "runs" &&
|
|
statePath.includes("generated_images")
|
|
);
|
|
}
|
|
|
|
function normalizedChangedFilePath(
|
|
value: unknown,
|
|
options: { allowRelative?: boolean } = {}
|
|
): string | null {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
const path = value.trim();
|
|
if (!path || isStructuredPayloadPath(path)) {
|
|
return null;
|
|
}
|
|
if (!options.allowRelative && !path.startsWith("/workspace/")) {
|
|
return null;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
function applyShortestUniqueFileLabels(
|
|
files: WorkspaceAgentChangedFile[]
|
|
): WorkspaceAgentChangedFile[] {
|
|
const suffixByPath = shortestUniquePathSuffixByPath(
|
|
files.map((file) => file.path)
|
|
);
|
|
return files.map((file) => ({
|
|
...file,
|
|
label: suffixByPath.get(file.path) ?? file.label
|
|
}));
|
|
}
|
|
|
|
function shortestUniquePathSuffixByPath(paths: string[]): Map<string, string> {
|
|
const partsByPath = new Map(
|
|
paths.map((path) => [path, splitPathSegments(path)])
|
|
);
|
|
const labels = new Map<string, string>();
|
|
const unresolved = new Set(paths);
|
|
|
|
for (let depth = 1; unresolved.size > 0; depth += 1) {
|
|
const grouped = new Map<string, string[]>();
|
|
|
|
for (const path of unresolved) {
|
|
const parts = partsByPath.get(path) ?? [];
|
|
const suffix = parts.slice(-Math.min(depth, parts.length)).join("/");
|
|
const fallback = parts.at(-1) ?? path;
|
|
const label = suffix || fallback;
|
|
const group = grouped.get(label);
|
|
if (group) {
|
|
group.push(path);
|
|
} else {
|
|
grouped.set(label, [path]);
|
|
}
|
|
}
|
|
|
|
let resolvedCount = 0;
|
|
for (const [label, group] of grouped) {
|
|
if (group.length !== 1) {
|
|
continue;
|
|
}
|
|
const path = group[0];
|
|
if (!path) {
|
|
continue;
|
|
}
|
|
labels.set(path, label);
|
|
unresolved.delete(path);
|
|
resolvedCount += 1;
|
|
}
|
|
|
|
if (resolvedCount === 0) {
|
|
for (const path of unresolved) {
|
|
labels.set(path, path);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return labels;
|
|
}
|
|
|
|
function splitPathSegments(path: string): string[] {
|
|
return path.trim().split(/[\\/]/).filter(Boolean);
|
|
}
|
|
|
|
function isSuccessfulFileChangeToolMessage(
|
|
message: AgentActivityMessage
|
|
): boolean {
|
|
if (normalizeToken(message.kind) !== "tool_call") {
|
|
return false;
|
|
}
|
|
if (!isSuccessfulFileChangeStatus(message.status ?? undefined)) {
|
|
return false;
|
|
}
|
|
const payload = objectValue(message.payload);
|
|
if (!isSuccessfulFileChangePayloadStatus(payload)) {
|
|
return false;
|
|
}
|
|
return hasFileChangeSignal(payload);
|
|
}
|
|
|
|
function isSuccessfulFileChangePayloadStatus(
|
|
payload: Record<string, unknown> | null
|
|
): boolean {
|
|
if (!payload) {
|
|
return true;
|
|
}
|
|
if (!isSuccessfulFileChangeRecordStatus(payload)) {
|
|
return false;
|
|
}
|
|
const output = objectValue(payload.output);
|
|
if (output && !isSuccessfulFileChangeRecordStatus(output)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function isSuccessfulFileChangeRecordStatus(
|
|
record: Record<string, unknown>
|
|
): boolean {
|
|
const status = stringValue(record.status);
|
|
if (status && !isSuccessfulFileChangeStatus(status)) {
|
|
return false;
|
|
}
|
|
const success = booleanValue(record.success);
|
|
if (success === false) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function isSuccessfulFileChangeStatus(value: string | undefined): boolean {
|
|
const normalizedStatus = normalizeToken(value);
|
|
return (
|
|
!normalizedStatus ||
|
|
normalizedStatus === "completed" ||
|
|
normalizedStatus === "success" ||
|
|
normalizedStatus === "succeeded" ||
|
|
normalizedStatus === "ok"
|
|
);
|
|
}
|
|
|
|
function hasFileChangeSignal(payload: Record<string, unknown> | null): boolean {
|
|
if (!payload) {
|
|
return false;
|
|
}
|
|
return [
|
|
payload,
|
|
objectValue(payload.input),
|
|
objectValue(payload.output)
|
|
].some((record) => record !== null && recordHasFileChangeToolSignal(record));
|
|
}
|
|
|
|
function recordHasFileChangeToolSignal(
|
|
record: Record<string, unknown>
|
|
): boolean {
|
|
const activityKind = stringValue(record.activityKind);
|
|
if (
|
|
activityKind &&
|
|
isFileChangeNormalizedToolName(normalizeToolName(activityKind))
|
|
) {
|
|
return true;
|
|
}
|
|
if (stringValue(record.fileChangeKind)) {
|
|
return true;
|
|
}
|
|
const toolCall = objectValue(record.toolCall);
|
|
const toolCallKind = normalizeToken(stringValue(toolCall?.kind) ?? undefined);
|
|
if (
|
|
toolCallKind === "write" ||
|
|
toolCallKind === "edit" ||
|
|
toolCallKind === "delete"
|
|
) {
|
|
return true;
|
|
}
|
|
const toolName = normalizeToolName(
|
|
stringValue(record.toolName) ??
|
|
stringValue(record.title) ??
|
|
stringValue(record.name) ??
|
|
""
|
|
);
|
|
return isFileChangeNormalizedToolName(toolName);
|
|
}
|
|
|
|
function isFileChangeNormalizedToolName(normalizedToolName: string): boolean {
|
|
if (!normalizedToolName) {
|
|
return false;
|
|
}
|
|
const exactMatches = new Set([
|
|
"write",
|
|
"writefile",
|
|
"create",
|
|
"createfile",
|
|
"delete",
|
|
"deletefile",
|
|
"edit",
|
|
"editfile",
|
|
"multiedit",
|
|
"applypatch",
|
|
"move",
|
|
"notebookedit"
|
|
]);
|
|
if (exactMatches.has(normalizedToolName)) {
|
|
return true;
|
|
}
|
|
for (const prefix of exactMatches) {
|
|
if (normalizedToolName.startsWith(`${prefix}/`)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function fileChangePaths(
|
|
files: readonly unknown[] | null,
|
|
normalizePath: ChangedFilePathNormalizer = defaultChangedFilePathNormalizer
|
|
): string[] {
|
|
if (!files) {
|
|
return [];
|
|
}
|
|
return files
|
|
.map((file) => normalizePath(objectValue(file)?.path))
|
|
.filter((path): path is string => path !== null);
|
|
}
|
|
|
|
function changeMapPaths(
|
|
value: unknown,
|
|
normalizePath: ChangedFilePathNormalizer = defaultChangedFilePathNormalizer
|
|
): string[] {
|
|
return fileChangePathsFromChanges(value)
|
|
.map((path) => normalizePath(path))
|
|
.filter((path): path is string => path !== null);
|
|
}
|
|
|
|
function pathsValue(
|
|
value: unknown,
|
|
normalizePath: ChangedFilePathNormalizer = defaultChangedFilePathNormalizer
|
|
): string[] {
|
|
const paths = arrayValue(value);
|
|
if (!paths) {
|
|
return [];
|
|
}
|
|
return paths
|
|
.map((path) => normalizePath(path))
|
|
.filter((path): path is string => path !== null);
|
|
}
|
|
|
|
function objectValue(value: unknown): Record<string, unknown> | null {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function arrayValue(value: unknown): readonly unknown[] | null {
|
|
return Array.isArray(value) ? value : null;
|
|
}
|
|
|
|
function stringValue(value: unknown): string | null {
|
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
}
|
|
|
|
function booleanValue(value: unknown): boolean | null {
|
|
return typeof value === "boolean" ? value : null;
|
|
}
|
|
|
|
function dedupeStrings(values: Array<string | null>): string[] {
|
|
return [
|
|
...new Set(
|
|
values.filter(
|
|
(value): value is string =>
|
|
typeof value === "string" && value.length > 0
|
|
)
|
|
)
|
|
];
|
|
}
|
|
|
|
function normalizeToolName(value: string): string {
|
|
return value
|
|
.trim()
|
|
.toLowerCase()
|
|
.replaceAll("_", "")
|
|
.replaceAll("-", "")
|
|
.replaceAll(" ", "");
|
|
}
|
|
|
|
function isStructuredPayloadPath(path: string): boolean {
|
|
if (/[\r\n]/.test(path)) {
|
|
return true;
|
|
}
|
|
if (!path.startsWith("{") && !path.startsWith("[")) {
|
|
return false;
|
|
}
|
|
try {
|
|
JSON.parse(path);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function resolveWorkspaceAgentSessionMessages(
|
|
sessionMessagesById: Record<string, AgentActivityMessage[]> | undefined,
|
|
session: AgentActivitySession
|
|
): AgentActivityMessage[] {
|
|
if (!sessionMessagesById) {
|
|
return [];
|
|
}
|
|
for (const alias of workspaceAgentSessionMessageAliases(session)) {
|
|
const messages = sessionMessagesById[alias];
|
|
if (messages) {
|
|
return messages;
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function normalizeToken(value: string | undefined): string {
|
|
return value?.trim().toLowerCase() ?? "";
|
|
}
|