689 lines · 25.1 KB · TypeScript
RawDownload
import {  isToolLifecycleItemType,  type AssetResource,  type RuntimeItemStatus,  type ThreadId,  type ToolActivitySource,  type ToolLifecycleItemType,} from "@t3tools/contracts";import { classifyMarkdownImageSource } from "@t3tools/client-runtime/markdown-images";import { resolveMediaSource } from "@t3tools/client-runtime/media-source";import { parseChangeRequestUrl } from "@t3tools/shared/changeRequestUrl";import { isWorkspaceImagePreviewPath } from "@t3tools/shared/filePreview"; export function isWorktreeSetupActivity(kind: string): boolean {  return kind === "setup-script.requested" || kind === "setup-script.started";} export type WorkLogToolLifecycleStatus = RuntimeItemStatus | "stopped"; export interface WorkLogPresentationEntry {  readonly label: string;  readonly toolTitle?: string;  readonly toolData?: unknown;  readonly tone: "thinking" | "tool" | "info" | "error";  readonly command?: string;  readonly detail?: string;  readonly viewedImagePath?: string;  readonly changedFiles?: ReadonlyArray<string>;  readonly itemType?: ToolLifecycleItemType;  readonly requestKind?: string;  readonly turnId?: string | null;  readonly toolCallId?: string;  readonly toolLifecycleStatus?: string;  readonly sourceActivityKind?: string;  readonly taskId?: string;  readonly toolSource?: ToolActivitySource;} export type ToolGroupAction =  | "link-pr"  | "unlink-pr"  | "list-prs"  | "read"  | "edit"  | "command"  | "browser"  | "device"  | "code-search"  | "search"  | "other"  | "update"; export type ToolGroupSummaryKind =  | "pull-request"  | ToolGroupAction  | "dynamic-tool"  | "agent-tool"  | "tone-tool"  | "mixed"; export function normalizeCompactToolLabel(value: string): string {  return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim();} const T3_MCP_TOOL_LABELS: Record<  string,  readonly [action: string, running: string, completed: string, detail: string]> = {  link_pull_request: ["Link", "Linking", "Linked", "a pull request"],  unlink_pull_request: ["Unlink", "Unlinking", "Unlinked", "a pull request"],  list_thread_pull_requests: ["Check", "Checking", "Checked", "linked pull requests"],  orchestrator_capabilities: ["Get", "Getting", "Got", "orchestration capabilities"],  delegate_task: ["Delegate", "Delegating", "Delegated", "a child task"],  task_status: ["Get", "Getting", "Got", "delegated task status"],  task_cancel: ["Cancel", "Canceling", "Canceled", "delegated task"],  schedule_task: ["Schedule", "Scheduling", "Scheduled", "a recurring task"],  list_scheduled_tasks: ["List", "Listing", "Listed", "scheduled tasks"],  update_scheduled_task: ["Update", "Updating", "Updated", "a scheduled task"],  delete_scheduled_task: ["Delete", "Deleting", "Deleted", "a scheduled task"],  create_threads: ["Create", "Creating", "Created", "T3 threads"],  t3_thread_start: ["Start", "Starting", "Started", "a T3 thread"],  t3_thread_list: ["List", "Listing", "Listed", "T3 threads"],  t3_thread_read: ["Read", "Reading", "Read", "a T3 thread"],  t3_thread_send: ["Send", "Sending", "Sent", "to a T3 thread"],  t3_thread_wait: ["Wait", "Waiting", "Waited", "for a T3 thread"],  t3_thread_interrupt: ["Interrupt", "Interrupting", "Interrupted", "a T3 thread"],  t3_worktree_handoff: ["Hand off", "Handing off", "Handed off", "thread to a git worktree"],  t3_worktree_status: ["Get", "Getting", "Got", "thread worktree status"],  preview_status: ["Get", "Getting", "Got", "preview browser status"],  preview_open: ["Open", "Opening", "Opened", "a page in the preview browser"],  preview_navigate: ["Navigate", "Navigating", "Navigated", "the preview browser"],  preview_snapshot: [    "Take a snapshot of",    "Taking a snapshot of",    "Took a snapshot of",    "the preview page",  ],  preview_click: ["Click", "Clicking", "Clicked", "in the preview browser"],  preview_press: ["Press", "Pressing", "Pressed", "a key in the preview browser"],  preview_type: ["Type", "Typing", "Typed", "in the preview browser"],  preview_scroll: ["Scroll", "Scrolling", "Scrolled", "the preview browser"],  preview_resize: ["Resize", "Resizing", "Resized", "the preview browser"],  preview_evaluate: ["Evaluate", "Evaluating", "Evaluated", "script in the preview browser"],  preview_wait_for: ["Wait", "Waiting", "Waited", "for the preview page"],  preview_set_appearance: ["Set", "Setting", "Set", "preview browser appearance"],  preview_recording_start: ["Start", "Starting", "Started", "recording the preview browser"],  preview_recording_stop: ["Stop", "Stopping", "Stopped", "recording the preview browser"],  device_list: ["List", "Listing", "Listed", "simulators and emulators"],  device_open: ["Open", "Opening", "Opened", "a device in the Device panel"],  device_screenshot: [    "Take a screenshot of",    "Taking a screenshot of",    "Took a screenshot of",    "the device",  ],  device_close: ["Close", "Closing", "Closed", "a device"],}; const PR_TOOL_ACTIONS: Readonly<Record<string, ToolGroupAction>> = {  link_pull_request: "link-pr",  unlink_pull_request: "unlink-pr",  list_thread_pull_requests: "list-prs",}; function resolveT3McpToolPresentation(  value: string | undefined,  status: string | undefined,  data?: unknown,) {  if (!value) return null;  const name = normalizeCompactToolLabel(value).replace(    /^(?:mcp__(?:t3-code|t3_code|t3code)__|(?:t3-code|t3_code|t3code)(?:[.:/]|\s*·\s*))/i,    "",  );  if (!Object.hasOwn(T3_MCP_TOOL_LABELS, name)) return null;   const [action, running, completed, detail] = T3_MCP_TOOL_LABELS[name]!;  const verb =    status === "inProgress"      ? running      : status === "completed"        ? completed        : status === "failed"          ? `Failed to ${action.toLowerCase()}`          : status === "declined"            ? `Declined to ${action.toLowerCase()}`            : status === "stopped"              ? `Stopped ${running.toLowerCase()}`              : running;   const actionKind = Object.hasOwn(PR_TOOL_ACTIONS, name) ? PR_TOOL_ACTIONS[name] : undefined;  const payload = asRecord(data);  const input =    asRecord(payload?.arguments) ?? asRecord(payload?.input) ?? asRecord(payload?.rawInput);  const urlTarget = typeof input?.url === "string" ? parseChangeRequestUrl(input.url) : null;  const number = urlTarget?.number ?? input?.number;  const target =    actionKind !== undefined &&    actionKind !== "list-prs" &&    typeof number === "number" &&    Number.isSafeInteger(number) &&    number > 0      ? `PR #${number}`      : detail;  return {    displayName: `${verb} ${target}`,    icon:      actionKind !== undefined        ? ("pull-request" as const)        : name.startsWith("preview_")          ? ("browser" as const)          : name.startsWith("device_")            ? ("device" as const)            : ("t3-code" as const),    ...(actionKind === undefined ? {} : { action: actionKind }),  };} /** Latest live activity stays present-tense unless the call itself failed, declined, or stopped. */export function liveActivityToolStatus(status: string | undefined, presentTense: boolean) {  if (status === "failed" || status === "declined" || status === "stopped") return status;  if (presentTense || status === "inProgress") return "inProgress";  return "completed";} /** Resolves tool identity before choosing labels or icons in either client. */export function resolveWorkEntryToolPresentation(  entry: Pick<WorkLogPresentationEntry, "label" | "toolTitle" | "toolData" | "toolLifecycleStatus">,  fallbackStatus?: "inProgress" | "completed",) {  const status = entry.toolLifecycleStatus ?? fallbackStatus;  const data = entry.toolData;  if (data !== null && typeof data === "object") {    if (      "server" in data &&      typeof data.server === "string" &&      "tool" in data &&      typeof data.tool === "string"    ) {      return resolveT3McpToolPresentation(`${data.server}.${data.tool}`, status, data);    }    if ("toolName" in data && typeof data.toolName === "string") {      return resolveT3McpToolPresentation(data.toolName, status, data);    }  }   return (    resolveT3McpToolPresentation(entry.toolTitle, status, data) ??    resolveT3McpToolPresentation(entry.label, status, data)  );} function asRecord(value: unknown): Record<string, unknown> | null {  return value !== null && typeof value === "object" && !Array.isArray(value)    ? (value as Record<string, unknown>)    : null;} function nonEmptyString(value: unknown): string | null {  return typeof value === "string" && value.trim().length > 0 ? value : null;} function commandResultContent(value: unknown): string | null {  const direct = nonEmptyString(value);  if (direct) return direct;   const directContent = Array.isArray(value) ? value : null;  const record = asRecord(value);  const content = record?.content;  const contentText = nonEmptyString(content);  if (contentText) return contentText;  const blocks = directContent ?? (Array.isArray(content) ? content : null);  if (!blocks) return null;   const chunks = blocks.flatMap((entry) => {    const text = nonEmptyString(entry) ?? nonEmptyString(asRecord(entry)?.text);    return text ? [text] : [];  });  return chunks.length > 0 ? chunks.join("\n") : null;} /** Returns provider command output before it is formatted for a work-log row. */export function extractCommandOutputText(dataValue: unknown): string | null {  const data = asRecord(dataValue);  const item = asRecord(data?.item);  const itemResult = asRecord(item?.result);  const rawOutput = asRecord(data?.rawOutput);  const outputStreams = [    nonEmptyString(rawOutput?.stdout),    nonEmptyString(rawOutput?.stderr),  ].filter((value): value is string => value !== null);  const acpContent = Array.isArray(data?.content)    ? data.content        .flatMap((entryValue) => {          const entry = asRecord(entryValue);          const content = asRecord(entry?.content);          const text = entry?.type === "content" ? nonEmptyString(content?.text) : null;          return text ? [text] : [];        })        .join("\n")    : null;   const candidates = [    item?.aggregatedOutput,    itemResult?.content,    data?.rawOutput,    rawOutput?.content,    outputStreams.length > 0 ? outputStreams.join("\n") : null,    rawOutput?.output,    acpContent,    data?.result,  ];  for (const candidate of candidates) {    const text = commandResultContent(candidate);    if (text) return text;  }  return null;} /** * Ingestion caps tool details at 180 chars and appends "...", so a long command * echo no longer equals the command it repeats. Treat a truncated prefix of the * command as the same echo. */function textRepeatsCommand(text: string, commands: ReadonlyArray<string | null>): boolean {  const truncated = text.endsWith("...")    ? text.slice(0, -3)    : text.endsWith("\u2026")      ? text.slice(0, -1)      : null;  return commands.some((candidate) => {    const command = candidate?.trim();    if (!command) return false;    if (command === text) return true;    return (      truncated !== null &&      truncated.length > 0 &&      command.length > truncated.length &&      command.startsWith(truncated)    );  });} /** * Decides whether a command row's `detail` is a synthetic echo of the command * rather than real output. OpenCode stores completed output in `detail` with no * other output channel, so plain equality is only treated as synthetic when the * payload shape shows the detail came from the command: Codex item metadata, * an ACP tool call (`data.toolCallId`, `kind: "execute"`), a Claude tool-name * prefix, or no structured command at all. */export function commandDetailRepeatsCommand(input: {  readonly detail: string;  readonly command: string | null;  readonly rawCommand: string | null;  readonly toolName: unknown;  readonly data: unknown;}): boolean {  const toolName = nonEmptyString(input.toolName)?.trim();  const detail = input.detail.trim();  const commands = [input.command, input.rawCommand];  if (toolName) {    const prefix = `${toolName}:`;    if (detail.toLowerCase().startsWith(prefix.toLowerCase())) {      const unprefixed = detail.slice(prefix.length).trim();      if (textRepeatsCommand(unprefixed, commands)) return true;    }  }   if (!textRepeatsCommand(detail, commands)) return false;   const data = asRecord(input.data);  const item = asRecord(data?.item);  const itemInput = asRecord(item?.input);  const itemResult = asRecord(item?.result);  const hasStructuredCommand = [    item?.command,    itemInput?.command,    itemResult?.command,    data?.command,  ].some((value) =>    Array.isArray(value)      ? value.some((part) => nonEmptyString(part) !== null)      : nonEmptyString(value) !== null,  );  return (    !hasStructuredCommand ||    item !== null ||    data?.toolCallId !== undefined ||    nonEmptyString(data?.kind)?.toLowerCase() === "execute"  );} export function workLogEntryIsToolLike(entry: WorkLogPresentationEntry): boolean {  if (entry.tone === "tool" || entry.tone === "thinking" || entry.tone === "error") return true;  if (entry.command !== undefined && entry.command.trim().length > 0) return true;  if (entry.requestKind !== undefined) return true;  return entry.itemType !== undefined && isToolLifecycleItemType(entry.itemType);} /** Maps item and task status to the status shown on a work-log row. */export function extractWorkLogToolLifecycleStatus(  payloadValue: unknown,): WorkLogToolLifecycleStatus | undefined {  const payload = asRecord(payloadValue);  switch (payload?.status) {    case "pending":    case "running":    case "waiting":      return "inProgress";    case "cancelled":    case "interrupted":      return "stopped";    case "idle":      // A batch becomes idle when its parent turn ends. Other idle tasks can resume.      return payload.taskType === "subagent_batch" ? "stopped" : undefined;    case "inProgress":    case "completed":    case "failed":    case "declined":    case "stopped":      return payload.status;    default:      return undefined;  }} // Some providers report completion even when the output describes a failure.function toolDetailTextLooksLikeFailure(text: string): boolean {  const normalized = text.toLowerCase();  return (    normalized.includes("file not found") ||    normalized.includes("no files found") ||    normalized.includes("enoent") ||    normalized.includes("no such file or directory") ||    normalized.includes("no such file") ||    normalized.includes("commandnotfoundexception") ||    normalized.includes("command not found") ||    (normalized.includes("cannot find path") && normalized.includes("because it does not exist")) ||    (normalized.includes("is not recognized") && normalized.includes("the term '")) ||    normalized.includes("is not recognized as the name of a cmdlet") ||    normalized.includes("a parameter cannot be found that matches parameter name") ||    /<exited with exit code\s+[1-9]\d*\s*>/i.test(text) ||    /exit(?:ed)? with exit code\s+[1-9]\d*/i.test(text) ||    /exit code\s*[:\s]\s*[1-9]\d*\b/i.test(text)  );} function workEntryIndicatesToolFailureFromOutput(  entry: WorkLogPresentationEntry,  includeCommand: boolean,): boolean {  if (    entry.tone === "error" ||    entry.toolLifecycleStatus === "failed" ||    entry.toolLifecycleStatus === "declined"  ) {    return true;  }  if (!workLogEntryIsToolLike(entry)) return false;  const output = includeCommand    ? [entry.detail, entry.command].filter(Boolean).join("\n")    : (entry.detail ?? "");  return output.length > 0 && toolDetailTextLooksLikeFailure(output);} /** Includes legacy activities that stored error output in the command field. */export function workEntryIndicatesToolFailure(entry: WorkLogPresentationEntry): boolean {  return workEntryIndicatesToolFailureFromOutput(entry, true);} /** Checks rendered output without treating the user's command as an error. */export function workEntryDisplayIndicatesToolFailure(entry: WorkLogPresentationEntry): boolean {  return workEntryIndicatesToolFailureFromOutput(entry, false);} /** Decides whether the row can show a success marker. */export function workEntryIndicatesToolSuccess(entry: WorkLogPresentationEntry): boolean {  return (    workLogEntryIsToolLike(entry) &&    !workEntryIndicatesToolFailure(entry) &&    entry.tone !== "thinking" &&    entry.toolLifecycleStatus !== "inProgress" &&    entry.toolLifecycleStatus !== "stopped"  );} function workLogEntryIsLocalCodeSearch(entry: WorkLogPresentationEntry): boolean {  return (    entry.itemType === "web_search" &&    /\bgrep\b/i.test(normalizeCompactToolLabel(entry.toolTitle ?? entry.label))  );} export function toolGroupAction(entry: WorkLogPresentationEntry): ToolGroupAction {  if (    entry.sourceActivityKind === "approval.requested" ||    entry.sourceActivityKind === "approval.resolved" ||    entry.sourceActivityKind === "provider.approval.respond.failed"  ) {    return "update";  }  const presentation = resolveWorkEntryToolPresentation(entry);  if (presentation?.action !== undefined) return presentation.action;  if (presentation?.icon === "browser") return "browser";  if (presentation?.icon === "device") return "device";  if (    entry.requestKind === "file-read" ||    entry.itemType === "image_view" ||    entry.viewedImagePath !== undefined ||    (entry.itemType === "dynamic_tool_call" &&      entry.toolTitle?.trim().toLowerCase() === "read file")  ) {    return "read";  }  if (    entry.requestKind === "file-change" ||    entry.itemType === "file_change" ||    (entry.changedFiles?.length ?? 0) > 0  ) {    return "edit";  }  if (entry.requestKind === "command" || entry.itemType === "command_execution" || entry.command) {    return "command";  }  if (workLogEntryIsLocalCodeSearch(entry)) return "code-search";  if (entry.itemType === "web_search") return "search";  return workLogEntryIsToolLike(entry) ? "other" : "update";} export function workEntryViewedImagePath(entry: WorkLogPresentationEntry): string | null {  const viewedImagePath = entry.viewedImagePath?.trim();  if (    viewedImagePath !== undefined &&    !/[\r\n]/.test(viewedImagePath) &&    isWorkspaceImagePreviewPath(viewedImagePath)  ) {    return viewedImagePath;  }  const detail = entry.detail?.trim();

Lines 1–500 of 689.