import * as Option from "effect/Option";import * as Arr from "effect/Array";import * as Schema from "effect/Schema";import { isBackgroundTaskActivity } from "@t3tools/client-runtime/state/subagentRuntime";import { isWorktreeSetupActivity } from "@t3tools/client-runtime/work-log/presentation";import { ApprovalRequestId, isToolLifecycleItemType, type OrchestrationLatestTurn, type OrchestrationThreadActivity, type OrchestrationProposedPlanId, ProviderDriverKind, ProviderApprovalOption, ProviderRequestKind, type ToolLifecycleItemType, type UserInputQuestion, type ThreadId, type TurnId,} from "@t3tools/contracts"; import type { ChatMessage, ProposedPlan, SessionPhase, Thread, ThreadSession, TurnDiffSummary,} from "./types"; export type ProviderPickerKind = ProviderDriverKind; export const PROVIDER_OPTIONS: Array<{ value: ProviderPickerKind; label: string; available: boolean; /** Shown on the model picker sidebar when relevant */ pickerSidebarBadge?: "new" | "soon";}> = [ { value: ProviderDriverKind.make("codex"), label: "Codex", available: true }, { value: ProviderDriverKind.make("claudeAgent"), label: "Claude", available: true }, { value: ProviderDriverKind.make("opencode"), label: "OpenCode", available: true, pickerSidebarBadge: "new", }, { value: ProviderDriverKind.make("cursor"), label: "Cursor", available: true, pickerSidebarBadge: "new", }, { value: ProviderDriverKind.make("grok"), label: "Grok", available: true, pickerSidebarBadge: "new", },]; export type WorkLogToolLifecycleStatus = | "inProgress" | "completed" | "failed" | "declined" | "stopped"; export interface WorkLogEntry { id: string; createdAt: string; turnId?: TurnId | null; /** Stable provider identity across in-progress and completed lifecycle updates. */ toolCallId?: string; label: string; detail?: string; command?: string; rawCommand?: string; changedFiles?: ReadonlyArray<string>; tone: "thinking" | "tool" | "info" | "error"; toolTitle?: string; toolData?: unknown; itemType?: ToolLifecycleItemType; requestKind?: PendingApproval["requestKind"]; /** From runtime item / task payload `status` when present (e.g. tool.updated). */ toolLifecycleStatus?: WorkLogToolLifecycleStatus; /** Originating orchestration activity kind (e.g. `user-input.requested`) for row chrome. */ sourceActivityKind?: OrchestrationThreadActivity["kind"]; /** Grouping key for subagent lifecycle rows (one row per agent). */ taskId?: string; /** Agent role (subagent_type) for labeled timeline rows. */ agentRole?: string; /** * Present on agent-spawn CTA rows: one per workflow run or per-turn batch * of direct spawns. The row renders as a call-to-action ("Kicked off N * subagents") whose live status is derived from the agent panel model at * render time; clicking opens the Agents panel. */ agentSpawn?: { /** Workflow coordinator taskId, or null for a direct-spawn batch. */ workflowId: string | null; agentTaskIds: ReadonlyArray<string>; };} const workLogCollapseKey = Symbol(); interface DerivedWorkLogEntry extends WorkLogEntry { sourceActivityKind: OrchestrationThreadActivity["kind"]; [workLogCollapseKey]?: string; toolCallId?: string; isWorkflowCoordinator?: boolean; /** Shell/monitor/plan tasks: ordinary work-log rows, never spawn CTAs. */ isBackgroundTask?: boolean;} const derivedWorkLogEntryByActivity = new WeakMap< OrchestrationThreadActivity, DerivedWorkLogEntry>(); export interface PendingApproval { requestId: ApprovalRequestId; requestKind: ProviderRequestKind; createdAt: string; detail?: string; appName?: string; options?: ReadonlyArray<ProviderApprovalOption>;} const isProviderRequestKind = Schema.is(ProviderRequestKind);const isProviderApprovalOption = Schema.is(ProviderApprovalOption); export interface PendingUserInput { requestId: ApprovalRequestId; createdAt: string; questions: ReadonlyArray<UserInputQuestion>;} export interface ActivePlanState { createdAt: string; turnId: TurnId | null; explanation?: string | null; steps: Array<{ durationMs?: number; step: string; status: "pending" | "inProgress" | "completed"; }>;} export interface LatestProposedPlanState { id: OrchestrationProposedPlanId; createdAt: string; updatedAt: string; turnId: TurnId | null; planMarkdown: string; implementedAt: string | null; implementationThreadId: ThreadId | null;} export type TimelineEntry = | { id: string; kind: "message"; createdAt: string; message: ChatMessage; } | { id: string; kind: "proposed-plan"; createdAt: string; proposedPlan: ProposedPlan; } | { id: string; kind: "work"; createdAt: string; entry: WorkLogEntry; }; export function workLogEntryIsToolLike(entry: WorkLogEntry): 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);} /** Heuristic: providers often emit successful lifecycle status while error text lives in `detail` / `command`. */function toolDetailTextLooksLikeFailure(text: string): boolean { const t = text.toLowerCase(); if (t.includes("file not found")) { return true; } if (t.includes("no files found")) { return true; } if ( t.includes("enoent") || t.includes("no such file or directory") || t.includes("no such file") ) { return true; } if (t.includes("cannot find path") && t.includes("because it does not exist")) { return true; } if (t.includes("commandnotfoundexception")) { return true; } if (t.includes("is not recognized as the name of a cmdlet")) { return true; } if (t.includes("is not recognized") && t.includes("the term '")) { return true; } if (t.includes("a parameter cannot be found that matches parameter name")) { return true; } if (t.includes("command not found")) { return true; } if (/<exited with exit code\s+[1-9]\d*\s*>/i.test(text)) { return true; } if (/exit(?:ed)? with exit code\s+[1-9]\d*/i.test(text)) { return true; } if (/exit code\s*[:\s]\s*[1-9]\d*\b/i.test(text)) { return true; } return false;} function workEntryIndicatesToolFailureFromOutput( entry: WorkLogEntry, includeCommand: boolean,): boolean { if (entry.tone === "error") { return true; } const ls = entry.toolLifecycleStatus; if (ls === "failed" || ls === "declined") { return true; } if (!workLogEntryIsToolLike(entry)) { return false; } const parts: string[] = []; if (entry.detail) { parts.push(entry.detail); } if (includeCommand && entry.command) { parts.push(entry.command); } const blob = parts.join("\n"); if (blob.length === 0) { return false; } return toolDetailTextLooksLikeFailure(blob);} /** True when a tool failed, including providers that put error output in `command`. */export function workEntryIndicatesToolFailure(entry: WorkLogEntry): boolean { return workEntryIndicatesToolFailureFromOutput(entry, true);} /** True when the rendered result indicates failure. The command itself is user intent, not output. */export function workEntryDisplayIndicatesToolFailure(entry: WorkLogEntry): boolean { return workEntryIndicatesToolFailureFromOutput(entry, false);} /** Severe failures keep the red treatment ordinary tool failures lost: runtime * errors and orchestration `*.failed` activities (provider.turn.start.failed, * checkpoint.capture.failed, ...) mean the turn or a core side effect broke, * not that a command exited nonzero. */export function workEntrySignalsSevereFailure(entry: WorkLogEntry): boolean { return ( entry.sourceActivityKind === "runtime.error" || entry.sourceActivityKind?.endsWith(".failed") === true );} /** Tool/command row completed without failure (blue check affordance). */export function workEntryIndicatesToolSuccess(entry: WorkLogEntry): boolean { if (!workLogEntryIsToolLike(entry)) { return false; } if (workEntryIndicatesToolFailure(entry)) { return false; } if (entry.tone === "thinking") { return false; } const ls = entry.toolLifecycleStatus; if (ls === "failed" || ls === "declined") { return false; } if (ls === "inProgress") { return false; } if (ls === "stopped") { return false; } return true;} /** Tool-like row with neither clear success nor failure (empty, incomplete, in progress, etc.). */export function workEntryIndicatesToolNeutralStatus(entry: WorkLogEntry): boolean { // Spawn CTA rows are never neutral-hidden: mid-run they derive from // task.progress (tone "thinking") and the neutral filter was swallowing // them exactly while the fleet ran — the one moment they matter most. if (entry.agentSpawn !== undefined) { return false; } if (!workLogEntryIsToolLike(entry)) { return false; } if (workEntryIndicatesToolFailure(entry)) { return false; } if (workEntryIndicatesToolSuccess(entry)) { return false; } return true;} export function formatDuration(durationMs: number): string { if (!Number.isFinite(durationMs) || durationMs < 0) return "0ms"; if (durationMs < 1_000) return `${Math.max(1, Math.round(durationMs))}ms`; if (durationMs < 10_000) { const tenths = Math.round(durationMs / 100) / 10; // 9.95s+ rounds up to the next bucket — render "10s", not "10.0s". return tenths >= 10 ? "10s" : `${tenths.toFixed(1)}s`; } if (durationMs < 60_000) return `${Math.round(durationMs / 1_000)}s`; const minutes = Math.floor(durationMs / 60_000); const seconds = Math.round((durationMs % 60_000) / 1_000); if (seconds === 0) return `${minutes}m`; if (seconds === 60) return `${minutes + 1}m`; return `${minutes}m ${seconds}s`;} export function formatElapsed(startIso: string, endIso: string | undefined): string | null { if (!endIso) return null; const startedAt = Date.parse(startIso); const endedAt = Date.parse(endIso); if (Number.isNaN(startedAt) || Number.isNaN(endedAt) || endedAt < startedAt) { return null; } return formatDuration(endedAt - startedAt);} type LatestTurnTiming = Pick<OrchestrationLatestTurn, "turnId" | "startedAt" | "completedAt">;type SessionActivityState = Pick<NonNullable<Thread["session"]>, "status" | "activeTurnId">; export function isLatestTurnSettled( latestTurn: LatestTurnTiming | null, session: SessionActivityState | null,): boolean { if (!latestTurn?.startedAt) return false; if (!latestTurn.completedAt) return false; if (!session) return true; if (session.status === "running") return false; return true;} export function deriveActiveWorkStartedAt( latestTurn: LatestTurnTiming | null, session: SessionActivityState | null, sendStartedAt: string | null, latestUserMessageAt: string | null = null,): string | null { const runningTurnId = session?.status === "running" ? session.activeTurnId : null; if (runningTurnId !== null) { if (latestTurn?.turnId === runningTurnId) { return latestTurn.startedAt ?? sendStartedAt ?? latestUserMessageAt; } return sendStartedAt ?? latestUserMessageAt; } if (!isLatestTurnSettled(latestTurn, session)) { return latestTurn?.startedAt ?? sendStartedAt; } return sendStartedAt;} function requestKindFromRequestType(requestType: unknown): PendingApproval["requestKind"] | null { switch (requestType) { case "command_execution_approval": case "exec_command_approval": case "dynamic_tool_call": return "command"; case "file_read_approval": return "file-read"; case "file_change_approval": case "apply_patch_approval": return "file-change"; case "mcp_elicitation_approval": return "mcp-elicitation"; default: return null; }} function isStalePendingRequestFailureDetail(detail: string | undefined): boolean { const normalized = detail?.toLowerCase(); if (!normalized) { return false; } return ( normalized.includes("stale pending approval request") || normalized.includes("stale pending user-input request") || normalized.includes("unknown pending approval request") || normalized.includes("unknown pending permission request") || normalized.includes("unknown pending user-input request") || normalized.includes("unknown pending user input request") || normalized.includes("unknown pending codex user input request") );} export function derivePendingApprovals( activities: ReadonlyArray<OrchestrationThreadActivity>,): PendingApproval[] { const openByRequestId = new Map<ApprovalRequestId, PendingApproval>(); const ordered = [...activities].toSorted(compareActivitiesByOrder); for (const activity of ordered) { const payload = activity.payload && typeof activity.payload === "object" ? (activity.payload as Record<string, unknown>) : null; const requestId = payload && typeof payload.requestId === "string" ? ApprovalRequestId.make(payload.requestId) : null; const requestKind = payload && isProviderRequestKind(payload.requestKind) ? payload.requestKind : payload ? requestKindFromRequestType(payload.requestType) : null; const detail = payload && typeof payload.detail === "string" ? payload.detail : undefined; const appName = payload && typeof payload.appName === "string" ? payload.appName : undefined; const options = Array.isArray(payload?.options) ? payload.options.filter(isProviderApprovalOption) : undefined; if (activity.kind === "approval.requested" && requestId && requestKind) { openByRequestId.set(requestId, { requestId, requestKind, createdAt: activity.createdAt, ...(detail ? { detail } : {}), ...(appName ? { appName } : {}), ...(options && options.length > 0 ? { options } : {}), }); continue; } if (activity.kind === "approval.resolved" && requestId) { openByRequestId.delete(requestId); continue; } if ( activity.kind === "provider.approval.respond.failed" && requestId && isStalePendingRequestFailureDetail(detail) ) { openByRequestId.delete(requestId); continue; } } return [...openByRequestId.values()].toSorted((left, right) => left.createdAt.localeCompare(right.createdAt), );} function parseUserInputQuestions( payload: Record<string, unknown> | null,): ReadonlyArray<UserInputQuestion> | null { const questions = payload?.questions; if (!Array.isArray(questions)) { return null; } const parsed = questions .map<UserInputQuestion | null>((entry) => { if (!entry || typeof entry !== "object") return null; const question = entry as Record<string, unknown>; if ( typeof question.id !== "string" || typeof question.header !== "string" || typeof question.question !== "string" || !Array.isArray(question.options) ) {Lines 1–500 of 1840. Load more
import * as Option from "effect/Option"; import * as Arr from "effect/Array"; import * as Schema from "effect/Schema"; import { isBackgroundTaskActivity } from "@t3tools/client-runtime/state/subagentRuntime"; import { isWorktreeSetupActivity } from "@t3tools/client-runtime/work-log/presentation"; import { ApprovalRequestId, isToolLifecycleItemType, type OrchestrationLatestTurn, type OrchestrationThreadActivity, type OrchestrationProposedPlanId, ProviderDriverKind, ProviderApprovalOption, ProviderRequestKind, type ToolLifecycleItemType, type UserInputQuestion, type ThreadId, type TurnId, } from "@t3tools/contracts"; import type { ChatMessage, ProposedPlan, SessionPhase, Thread, ThreadSession, TurnDiffSummary, } from "./types"; export type ProviderPickerKind = ProviderDriverKind; export const PROVIDER_OPTIONS: Array<{ value: ProviderPickerKind; label: string; available: boolean; /** Shown on the model picker sidebar when relevant */ pickerSidebarBadge?: "new" | "soon"; }> = [ { value: ProviderDriverKind.make("codex"), label: "Codex", available: true }, { value: ProviderDriverKind.make("claudeAgent"), label: "Claude", available: true }, { value: ProviderDriverKind.make("opencode"), label: "OpenCode", available: true, pickerSidebarBadge: "new", }, { value: ProviderDriverKind.make("cursor"), label: "Cursor", available: true, pickerSidebarBadge: "new", }, { value: ProviderDriverKind.make("grok"), label: "Grok", available: true, pickerSidebarBadge: "new", }, ]; export type WorkLogToolLifecycleStatus = | "inProgress" | "completed" | "failed" | "declined" | "stopped"; export interface WorkLogEntry { id: string; createdAt: string; turnId?: TurnId | null; /** Stable provider identity across in-progress and completed lifecycle updates. */ toolCallId?: string; label: string; detail?: string; command?: string; rawCommand?: string; changedFiles?: ReadonlyArray<string>; tone: "thinking" | "tool" | "info" | "error"; toolTitle?: string; toolData?: unknown; itemType?: ToolLifecycleItemType; requestKind?: PendingApproval["requestKind"]; /** From runtime item / task payload `status` when present (e.g. tool.updated). */ toolLifecycleStatus?: WorkLogToolLifecycleStatus; /** Originating orchestration activity kind (e.g. `user-input.requested`) for row chrome. */ sourceActivityKind?: OrchestrationThreadActivity["kind"]; /** Grouping key for subagent lifecycle rows (one row per agent). */ taskId?: string; /** Agent role (subagent_type) for labeled timeline rows. */ agentRole?: string; /** * Present on agent-spawn CTA rows: one per workflow run or per-turn batch * of direct spawns. The row renders as a call-to-action ("Kicked off N * subagents") whose live status is derived from the agent panel model at * render time; clicking opens the Agents panel. */ agentSpawn?: { /** Workflow coordinator taskId, or null for a direct-spawn batch. */ workflowId: string | null; agentTaskIds: ReadonlyArray<string>; }; } const workLogCollapseKey = Symbol(); interface DerivedWorkLogEntry extends WorkLogEntry { sourceActivityKind: OrchestrationThreadActivity["kind"]; [workLogCollapseKey]?: string; toolCallId?: string; isWorkflowCoordinator?: boolean; /** Shell/monitor/plan tasks: ordinary work-log rows, never spawn CTAs. */ isBackgroundTask?: boolean; } const derivedWorkLogEntryByActivity = new WeakMap< OrchestrationThreadActivity, DerivedWorkLogEntry >(); export interface PendingApproval { requestId: ApprovalRequestId; requestKind: ProviderRequestKind; createdAt: string; detail?: string; appName?: string; options?: ReadonlyArray<ProviderApprovalOption>; } const isProviderRequestKind = Schema.is(ProviderRequestKind); const isProviderApprovalOption = Schema.is(ProviderApprovalOption); export interface PendingUserInput { requestId: ApprovalRequestId; createdAt: string; questions: ReadonlyArray<UserInputQuestion>; } export interface ActivePlanState { createdAt: string; turnId: TurnId | null; explanation?: string | null; steps: Array<{ durationMs?: number; step: string; status: "pending" | "inProgress" | "completed"; }>; } export interface LatestProposedPlanState { id: OrchestrationProposedPlanId; createdAt: string; updatedAt: string; turnId: TurnId | null; planMarkdown: string; implementedAt: string | null; implementationThreadId: ThreadId | null; } export type TimelineEntry = | { id: string; kind: "message"; createdAt: string; message: ChatMessage; } | { id: string; kind: "proposed-plan"; createdAt: string; proposedPlan: ProposedPlan; } | { id: string; kind: "work"; createdAt: string; entry: WorkLogEntry; }; export function workLogEntryIsToolLike(entry: WorkLogEntry): 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); } /** Heuristic: providers often emit successful lifecycle status while error text lives in `detail` / `command`. */ function toolDetailTextLooksLikeFailure(text: string): boolean { const t = text.toLowerCase(); if (t.includes("file not found")) { return true; } if (t.includes("no files found")) { return true; } if ( t.includes("enoent") || t.includes("no such file or directory") || t.includes("no such file") ) { return true; } if (t.includes("cannot find path") && t.includes("because it does not exist")) { return true; } if (t.includes("commandnotfoundexception")) { return true; } if (t.includes("is not recognized as the name of a cmdlet")) { return true; } if (t.includes("is not recognized") && t.includes("the term '")) { return true; } if (t.includes("a parameter cannot be found that matches parameter name")) { return true; } if (t.includes("command not found")) { return true; } if (/<exited with exit code\s+[1-9]\d*\s*>/i.test(text)) { return true; } if (/exit(?:ed)? with exit code\s+[1-9]\d*/i.test(text)) { return true; } if (/exit code\s*[:\s]\s*[1-9]\d*\b/i.test(text)) { return true; } return false; } function workEntryIndicatesToolFailureFromOutput( entry: WorkLogEntry, includeCommand: boolean, ): boolean { if (entry.tone === "error") { return true; } const ls = entry.toolLifecycleStatus; if (ls === "failed" || ls === "declined") { return true; } if (!workLogEntryIsToolLike(entry)) { return false; } const parts: string[] = []; if (entry.detail) { parts.push(entry.detail); } if (includeCommand && entry.command) { parts.push(entry.command); } const blob = parts.join("\n"); if (blob.length === 0) { return false; } return toolDetailTextLooksLikeFailure(blob); } /** True when a tool failed, including providers that put error output in `command`. */ export function workEntryIndicatesToolFailure(entry: WorkLogEntry): boolean { return workEntryIndicatesToolFailureFromOutput(entry, true); } /** True when the rendered result indicates failure. The command itself is user intent, not output. */ export function workEntryDisplayIndicatesToolFailure(entry: WorkLogEntry): boolean { return workEntryIndicatesToolFailureFromOutput(entry, false); } /** Severe failures keep the red treatment ordinary tool failures lost: runtime * errors and orchestration `*.failed` activities (provider.turn.start.failed, * checkpoint.capture.failed, ...) mean the turn or a core side effect broke, * not that a command exited nonzero. */ export function workEntrySignalsSevereFailure(entry: WorkLogEntry): boolean { return ( entry.sourceActivityKind === "runtime.error" || entry.sourceActivityKind?.endsWith(".failed") === true ); } /** Tool/command row completed without failure (blue check affordance). */ export function workEntryIndicatesToolSuccess(entry: WorkLogEntry): boolean { if (!workLogEntryIsToolLike(entry)) { return false; } if (workEntryIndicatesToolFailure(entry)) { return false; } if (entry.tone === "thinking") { return false; } const ls = entry.toolLifecycleStatus; if (ls === "failed" || ls === "declined") { return false; } if (ls === "inProgress") { return false; } if (ls === "stopped") { return false; } return true; } /** Tool-like row with neither clear success nor failure (empty, incomplete, in progress, etc.). */ export function workEntryIndicatesToolNeutralStatus(entry: WorkLogEntry): boolean { // Spawn CTA rows are never neutral-hidden: mid-run they derive from // task.progress (tone "thinking") and the neutral filter was swallowing // them exactly while the fleet ran — the one moment they matter most. if (entry.agentSpawn !== undefined) { return false; } if (!workLogEntryIsToolLike(entry)) { return false; } if (workEntryIndicatesToolFailure(entry)) { return false; } if (workEntryIndicatesToolSuccess(entry)) { return false; } return true; } export function formatDuration(durationMs: number): string { if (!Number.isFinite(durationMs) || durationMs < 0) return "0ms"; if (durationMs < 1_000) return `${Math.max(1, Math.round(durationMs))}ms`; if (durationMs < 10_000) { const tenths = Math.round(durationMs / 100) / 10; // 9.95s+ rounds up to the next bucket — render "10s", not "10.0s". return tenths >= 10 ? "10s" : `${tenths.toFixed(1)}s`; } if (durationMs < 60_000) return `${Math.round(durationMs / 1_000)}s`; const minutes = Math.floor(durationMs / 60_000); const seconds = Math.round((durationMs % 60_000) / 1_000); if (seconds === 0) return `${minutes}m`; if (seconds === 60) return `${minutes + 1}m`; return `${minutes}m ${seconds}s`; } export function formatElapsed(startIso: string, endIso: string | undefined): string | null { if (!endIso) return null; const startedAt = Date.parse(startIso); const endedAt = Date.parse(endIso); if (Number.isNaN(startedAt) || Number.isNaN(endedAt) || endedAt < startedAt) { return null; } return formatDuration(endedAt - startedAt); } type LatestTurnTiming = Pick<OrchestrationLatestTurn, "turnId" | "startedAt" | "completedAt">; type SessionActivityState = Pick<NonNullable<Thread["session"]>, "status" | "activeTurnId">; export function isLatestTurnSettled( latestTurn: LatestTurnTiming | null, session: SessionActivityState | null, ): boolean { if (!latestTurn?.startedAt) return false; if (!latestTurn.completedAt) return false; if (!session) return true; if (session.status === "running") return false; return true; } export function deriveActiveWorkStartedAt( latestTurn: LatestTurnTiming | null, session: SessionActivityState | null, sendStartedAt: string | null, latestUserMessageAt: string | null = null, ): string | null { const runningTurnId = session?.status === "running" ? session.activeTurnId : null; if (runningTurnId !== null) { if (latestTurn?.turnId === runningTurnId) { return latestTurn.startedAt ?? sendStartedAt ?? latestUserMessageAt; } return sendStartedAt ?? latestUserMessageAt; } if (!isLatestTurnSettled(latestTurn, session)) { return latestTurn?.startedAt ?? sendStartedAt; } return sendStartedAt; } function requestKindFromRequestType(requestType: unknown): PendingApproval["requestKind"] | null { switch (requestType) { case "command_execution_approval": case "exec_command_approval": case "dynamic_tool_call": return "command"; case "file_read_approval": return "file-read"; case "file_change_approval": case "apply_patch_approval": return "file-change"; case "mcp_elicitation_approval": return "mcp-elicitation"; default: return null; } } function isStalePendingRequestFailureDetail(detail: string | undefined): boolean { const normalized = detail?.toLowerCase(); if (!normalized) { return false; } return ( normalized.includes("stale pending approval request") || normalized.includes("stale pending user-input request") || normalized.includes("unknown pending approval request") || normalized.includes("unknown pending permission request") || normalized.includes("unknown pending user-input request") || normalized.includes("unknown pending user input request") || normalized.includes("unknown pending codex user input request") ); } export function derivePendingApprovals( activities: ReadonlyArray<OrchestrationThreadActivity>, ): PendingApproval[] { const openByRequestId = new Map<ApprovalRequestId, PendingApproval>(); const ordered = [...activities].toSorted(compareActivitiesByOrder); for (const activity of ordered) { const payload = activity.payload && typeof activity.payload === "object" ? (activity.payload as Record<string, unknown>) : null; const requestId = payload && typeof payload.requestId === "string" ? ApprovalRequestId.make(payload.requestId) : null; const requestKind = payload && isProviderRequestKind(payload.requestKind) ? payload.requestKind : payload ? requestKindFromRequestType(payload.requestType) : null; const detail = payload && typeof payload.detail === "string" ? payload.detail : undefined; const appName = payload && typeof payload.appName === "string" ? payload.appName : undefined; const options = Array.isArray(payload?.options) ? payload.options.filter(isProviderApprovalOption) : undefined; if (activity.kind === "approval.requested" && requestId && requestKind) { openByRequestId.set(requestId, { requestId, requestKind, createdAt: activity.createdAt, ...(detail ? { detail } : {}), ...(appName ? { appName } : {}), ...(options && options.length > 0 ? { options } : {}), }); continue; } if (activity.kind === "approval.resolved" && requestId) { openByRequestId.delete(requestId); continue; } if ( activity.kind === "provider.approval.respond.failed" && requestId && isStalePendingRequestFailureDetail(detail) ) { openByRequestId.delete(requestId); continue; } } return [...openByRequestId.values()].toSorted((left, right) => left.createdAt.localeCompare(right.createdAt), ); } function parseUserInputQuestions( payload: Record<string, unknown> | null, ): ReadonlyArray<UserInputQuestion> | null { const questions = payload?.questions; if (!Array.isArray(questions)) { return null; } const parsed = questions .map<UserInputQuestion | null>((entry) => { if (!entry || typeof entry !== "object") return null; const question = entry as Record<string, unknown>; if ( typeof question.id !== "string" || typeof question.header !== "string" || typeof question.question !== "string" || !Array.isArray(question.options) ) {