源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
import { resolveWorkspaceFilePathCandidate } from "../../../actions/workspaceFilePathCandidate";
|
||||
import type { AgentTurnSummaryFileVM } from "../contracts/agentTurnSummaryRowVM";
|
||||
|
||||
export type AgentTurnSummaryChangeType = AgentTurnSummaryFileVM["changeType"];
|
||||
export interface AgentTurnSummaryProjectionOptions {
|
||||
defaultCwd?: string | null;
|
||||
workspaceRoot?: string | null;
|
||||
}
|
||||
|
||||
export function normalizedFilePath(
|
||||
value: string | null,
|
||||
options: AgentTurnSummaryProjectionOptions = {}
|
||||
): string | null {
|
||||
const path = value?.trim() ?? "";
|
||||
if (!path || isStructuredPayloadPath(path) || isIgnoredFilePath(path)) {
|
||||
return null;
|
||||
}
|
||||
const candidate = resolveWorkspaceFilePathCandidate({
|
||||
path,
|
||||
workspaceRoot: options.workspaceRoot,
|
||||
basePath: options.defaultCwd
|
||||
});
|
||||
return candidate?.path ?? null;
|
||||
}
|
||||
|
||||
function isIgnoredFilePath(path: string): boolean {
|
||||
const normalizedPath = path.replace(/\\/g, "/").replace(/\/+$/, "");
|
||||
return normalizedPath === "/dev/null" || normalizedPath === "NUL";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export function applyShortestUniqueFileLabels(
|
||||
files: AgentTurnSummaryFileVM[]
|
||||
): AgentTurnSummaryFileVM[] {
|
||||
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 label =
|
||||
parts.slice(-Math.min(depth, parts.length)).join("/") ||
|
||||
parts.at(-1) ||
|
||||
path;
|
||||
grouped.set(label, [...(grouped.get(label) ?? []), path]);
|
||||
}
|
||||
let resolvedCount = 0;
|
||||
for (const [label, group] of grouped) {
|
||||
if (group.length !== 1 || !group[0]) {
|
||||
continue;
|
||||
}
|
||||
labels.set(group[0], label);
|
||||
unresolved.delete(group[0]);
|
||||
resolvedCount += 1;
|
||||
}
|
||||
if (resolvedCount === 0) {
|
||||
for (const path of unresolved) {
|
||||
labels.set(path, path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
export function splitFilePath(path: string): {
|
||||
fileName: string;
|
||||
directory: string | null;
|
||||
} {
|
||||
const normalized = path.trim();
|
||||
const parts = splitPathSegments(normalized);
|
||||
if (parts.length <= 1) {
|
||||
return { fileName: parts[0] ?? normalized, directory: null };
|
||||
}
|
||||
return {
|
||||
fileName: parts.at(-1) ?? normalized,
|
||||
directory: parts.slice(0, -1).join("/")
|
||||
};
|
||||
}
|
||||
|
||||
function splitPathSegments(path: string): string[] {
|
||||
return path.trim().split(/[\\/]/).filter(Boolean);
|
||||
}
|
||||
|
||||
export function normalizeChangeType(
|
||||
value: string | null
|
||||
): AgentTurnSummaryChangeType | null {
|
||||
switch ((value ?? "").trim().toLowerCase()) {
|
||||
case "add":
|
||||
case "added":
|
||||
case "create":
|
||||
case "created":
|
||||
case "new":
|
||||
return "created";
|
||||
case "modify":
|
||||
case "modified":
|
||||
case "edit":
|
||||
case "edited":
|
||||
case "change":
|
||||
case "changed":
|
||||
case "update":
|
||||
case "updated":
|
||||
return "modified";
|
||||
case "delete":
|
||||
case "deleted":
|
||||
case "remove":
|
||||
case "removed":
|
||||
return "deleted";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function isFailedToolStatus(value: string | null): boolean {
|
||||
const normalized = (value ?? "").trim().toLowerCase();
|
||||
return ["failed", "error", "errored", "canceled", "cancelled"].includes(
|
||||
normalized
|
||||
);
|
||||
}
|
||||
|
||||
export function objectValue(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
export function firstFileChangeValue(...values: unknown[]): unknown {
|
||||
for (const value of values) {
|
||||
if (Array.isArray(value) || objectValue(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function arrayValue(value: unknown): unknown[] | null {
|
||||
return Array.isArray(value) ? value : null;
|
||||
}
|
||||
|
||||
export function nestedTaskStepsFromPayload(
|
||||
payload: Record<string, unknown> | null,
|
||||
output: Record<string, unknown> | null
|
||||
): unknown[] {
|
||||
const candidates = [
|
||||
arrayValue(payload?.steps),
|
||||
arrayValue(output?.steps),
|
||||
arrayValue(objectValue(payload?.metadata)?.steps),
|
||||
arrayValue(objectValue(output?.metadata)?.steps)
|
||||
];
|
||||
return candidates.find((value) => value && value.length > 0) ?? [];
|
||||
}
|
||||
|
||||
export function nestedTaskStepStatusKind(
|
||||
step: Record<string, unknown>,
|
||||
fallback: string | null
|
||||
): string | null {
|
||||
return (
|
||||
stringValue(step.statusKind) ?? stringValue(step.status) ?? fallback ?? null
|
||||
);
|
||||
}
|
||||
|
||||
export function stringValue(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
export function literalStringValue(value: unknown): string | null {
|
||||
return typeof value === "string" ? value : null;
|
||||
}
|
||||
|
||||
export function firstNonEmptyString(
|
||||
...values: Array<string | null | undefined>
|
||||
): string | null {
|
||||
for (const value of values) {
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function firstPresentString(
|
||||
...values: Array<string | null | undefined>
|
||||
): string | null {
|
||||
for (const value of values) {
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user