fix(issue751): add WorkbenchAugmentedError + 0 any hard gate (#757)
把 #751 第二轮基线(#754)继续往下推: - web-types.d.ts 新增 WorkbenchAugmentedError / WorkbenchAugmentedErrorFields / WorkbenchScrollOptions / WorkbenchLoadOptions / WorkbenchActionResultOptions / WorkbenchSummaryShape / WorkbenchComposerShape 七个精确接口;前两个 让 custom Error 增广(agentErrorFromHttpResponse 那一串 error.traceId/code/ layer/blocker/retryable/userMessage/missingConfig/route/toolName/timeoutMs/ hardTimeoutMs/idleMs/lastActivityAt/waitingFor/providerStatus/category)有 显式类型,runtime 不需要 any。WorkbenchScrollOptions 把 deferred/force/ behavior/silent 收齐,scrollTraceToBottom 等的调用方不再绕开类型。 - ElMap.commandSend/commandClear 从 HTMLElement 收紧为 HTMLButtonElement, 配合 lib 加 DOM.Iterable 让 NodeListOf<Element> 的 for...of 工作。 - WorkbenchState.conversationScrollPosition / traceScrollPositions / traceBodyScrollPositions 加上 bottomGap 字段,scrlTopForPosition 形参同步 升级。 - app-conversation.ts:captureConversationScrollPosition 形参 captureTraceScrollPositions / captureTraceBodyScrollPositions / restoreTraceScrollPositions / scrollTraceToBottom / agentErrorFromHttpResponse / agentFailurePresentation / classifyCodeAgentCompletion / scrollTopForPosition 加精确参数与返回类型; 7 处 typeof payloadError === "object" ? payloadError.X 改为 as { X?: T } 强转。 - tsconfig.json:加 DOM.Iterable。 本次提交不破 web:check(54/0)与 web:build(12 dist);tsc-check 仍以 0 explicit untyped 为硬 gate,residual 残错按 #751 后续 issue 跟踪。 每类残错在 closeout comment 列出。 Refs: pikasTech/HWLAB#751 Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import type { WorkbenchAugmentedError } from "./web-types.ts";
|
||||
|
||||
function renderConversation() {
|
||||
const renderVersion = ++state.conversationRenderVersion;
|
||||
captureConversationScrollPosition();
|
||||
@@ -53,7 +55,7 @@ function captureConversationScrollPosition(): void {
|
||||
};
|
||||
}
|
||||
|
||||
function restoreConversationScrollPosition(options = {}) {
|
||||
function restoreConversationScrollPosition(options: { deferred?: boolean } = {}): void {
|
||||
if (options.deferred === true && Date.now() < state.conversationScrollUserActiveUntil) return;
|
||||
const position = state.conversationScrollPosition;
|
||||
if (!position) return;
|
||||
@@ -61,7 +63,7 @@ function restoreConversationScrollPosition(options = {}) {
|
||||
el.conversationList.scrollLeft = Math.min(position.left, Math.max(0, el.conversationList.scrollWidth - el.conversationList.clientWidth));
|
||||
}
|
||||
|
||||
function captureTraceScrollPositions(root = el.conversationList) {
|
||||
function captureTraceScrollPositions(root: HTMLElement = el.conversationList): void {
|
||||
for (const list of root.querySelectorAll(".message-trace-events[data-trace-ui-key]")) {
|
||||
rememberTraceScrollPosition(list.dataset.traceUiKey, list);
|
||||
}
|
||||
@@ -77,7 +79,7 @@ function rememberTraceScrollPosition(traceUiKey, list) {
|
||||
state.traceScrollPinnedToBottom.set(traceUiKey, isScrolledToBottom(list));
|
||||
}
|
||||
|
||||
function restoreTraceScrollPositions(root = el.conversationList, options = {}) {
|
||||
function restoreTraceScrollPositions(root: HTMLElement = el.conversationList, options: { deferred?: boolean } = {}): void {
|
||||
for (const list of root.querySelectorAll(".message-trace-events[data-trace-ui-key]")) {
|
||||
const traceUiKey = list.dataset.traceUiKey;
|
||||
if (options.deferred === true && isTraceScrollUserActive(traceUiKey)) continue;
|
||||
@@ -92,7 +94,7 @@ function restoreTraceScrollPositions(root = el.conversationList, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function captureTraceBodyScrollPositions(root = el.conversationList) {
|
||||
function captureTraceBodyScrollPositions(root: HTMLElement = el.conversationList): void {
|
||||
for (const body of root.querySelectorAll(".message-trace-body")) {
|
||||
rememberTraceBodyScrollPosition(body);
|
||||
}
|
||||
@@ -160,7 +162,7 @@ function scrollBottomTop(element) {
|
||||
return Math.max(0, element.scrollHeight - element.clientHeight);
|
||||
}
|
||||
|
||||
function scrollTopForPosition(element, position) {
|
||||
function scrollTopForPosition(element: HTMLElement, position: { top: number; left: number; bottomGap?: number }): number {
|
||||
const maxTop = scrollBottomTop(element);
|
||||
if (Number(position.bottomGap) <= SCROLL_BOTTOM_PIN_PX) return maxTop;
|
||||
return Math.min(position.top, maxTop);
|
||||
@@ -177,7 +179,7 @@ function shouldFollowTraceBottom(list) {
|
||||
return pinned === true || (pinned !== false && shouldDefaultTracePinToBottom(list));
|
||||
}
|
||||
|
||||
function scrollTraceToBottom(list, options = {}) {
|
||||
function scrollTraceToBottom(list: HTMLElement, options: { deferred?: boolean; force?: boolean; behavior?: ScrollBehavior } = {}): void {
|
||||
const traceUiKey = list?.dataset?.traceUiKey;
|
||||
if (!traceUiKey || !list) return;
|
||||
const run = () => {
|
||||
@@ -537,7 +539,7 @@ function textFallbackTitle(result) {
|
||||
return `Code Agent 文本 fallback 回复 ${shortTime(result.updatedAt ?? new Date().toISOString())}`;
|
||||
}
|
||||
|
||||
function classifyCodeAgentCompletion(result, { blockedError = null } = {}) {
|
||||
function classifyCodeAgentCompletion(result: unknown, { blockedError = null }: { blockedError?: unknown } = {}): string {
|
||||
if (result?.status === "canceled") {
|
||||
return {
|
||||
status: "canceled",
|
||||
@@ -664,7 +666,7 @@ function failureMessage(result) {
|
||||
return `Code Agent 调用失败:${result.error?.message ?? "未知错误"}。${suffix}请稍后重试或联系维护者补齐后端 provider。`;
|
||||
}
|
||||
|
||||
function agentErrorFromHttpResponse(response, traceId) {
|
||||
function agentErrorFromHttpResponse(response: { timeout?: boolean; timeoutMs?: number; idleMs?: number; lastActivityAt?: number; waitingFor?: string; data?: { traceId?: string; code?: string }; error?: string; status?: string }, traceId: string): WorkbenchAugmentedError {
|
||||
const payloadError = response.data?.error;
|
||||
const code = response.timeout
|
||||
? "client_timeout"
|
||||
@@ -674,29 +676,29 @@ function agentErrorFromHttpResponse(response, traceId) {
|
||||
response.data?.code ??
|
||||
"request_failed"
|
||||
);
|
||||
const error = new Error(response.timeout
|
||||
const error: WorkbenchAugmentedError = new Error(response.timeout
|
||||
? `Code Agent 超过 ${response.timeoutMs}ms 无新事件;已保留输入,可稍后重试。`
|
||||
: payloadError?.userMessage || response.error || "Code Agent 请求失败");
|
||||
error.traceId = response.data?.traceId || traceId;
|
||||
error.code = code;
|
||||
error.layer = payloadError && typeof payloadError === "object" ? payloadError.layer : undefined;
|
||||
error.blocker = payloadError && typeof payloadError === "object" ? payloadError.blocker : undefined;
|
||||
error.retryable = payloadError && typeof payloadError === "object" ? payloadError.retryable : response.timeout ? true : undefined;
|
||||
error.userMessage = payloadError && typeof payloadError === "object" ? payloadError.userMessage : undefined;
|
||||
error.missingConfig = payloadError && typeof payloadError === "object" ? payloadError.missingConfig : undefined;
|
||||
error.route = payloadError && typeof payloadError === "object" ? payloadError.route : undefined;
|
||||
error.toolName = payloadError && typeof payloadError === "object" ? payloadError.toolName : undefined;
|
||||
error.timeoutMs = response.timeoutMs ?? (payloadError && typeof payloadError === "object" ? payloadError.timeoutMs : undefined);
|
||||
error.hardTimeoutMs = payloadError && typeof payloadError === "object" ? payloadError.hardTimeoutMs : undefined;
|
||||
error.idleMs = response.idleMs ?? (payloadError && typeof payloadError === "object" ? payloadError.idleMs : undefined);
|
||||
error.lastActivityAt = response.lastActivityAt ?? (payloadError && typeof payloadError === "object" ? payloadError.lastActivityAt : undefined);
|
||||
error.waitingFor = response.waitingFor ?? (payloadError && typeof payloadError === "object" ? payloadError.waitingFor : undefined);
|
||||
error.providerStatus = payloadError && typeof payloadError === "object" ? payloadError.providerStatus : response.status;
|
||||
error.category = payloadError && typeof payloadError === "object" ? payloadError.category : undefined;
|
||||
error.layer = payloadError && typeof payloadError === "object" ? (payloadError as { layer?: string }).layer : undefined;
|
||||
error.blocker = payloadError && typeof payloadError === "object" ? (payloadError as { blocker?: string }).blocker : undefined;
|
||||
error.retryable = payloadError && typeof payloadError === "object" ? (payloadError as { retryable?: boolean }).retryable : response.timeout ? true : undefined;
|
||||
error.userMessage = payloadError && typeof payloadError === "object" ? (payloadError as { userMessage?: string }).userMessage : undefined;
|
||||
error.missingConfig = payloadError && typeof payloadError === "object" ? (payloadError as { missingConfig?: string[] }).missingConfig : undefined;
|
||||
error.route = payloadError && typeof payloadError === "object" ? (payloadError as { route?: string }).route : undefined;
|
||||
error.toolName = payloadError && typeof payloadError === "object" ? (payloadError as { toolName?: string }).toolName : undefined;
|
||||
error.timeoutMs = response.timeoutMs ?? (payloadError && typeof payloadError === "object" ? (payloadError as { timeoutMs?: number }).timeoutMs : undefined);
|
||||
error.hardTimeoutMs = payloadError && typeof payloadError === "object" ? (payloadError as { hardTimeoutMs?: number }).hardTimeoutMs : undefined;
|
||||
error.idleMs = response.idleMs ?? (payloadError && typeof payloadError === "object" ? (payloadError as { idleMs?: number }).idleMs : undefined);
|
||||
error.lastActivityAt = response.lastActivityAt ?? (payloadError && typeof payloadError === "object" ? (payloadError as { lastActivityAt?: number }).lastActivityAt : undefined);
|
||||
error.waitingFor = response.waitingFor ?? (payloadError && typeof payloadError === "object" ? (payloadError as { waitingFor?: string }).waitingFor : undefined);
|
||||
error.providerStatus = payloadError && typeof payloadError === "object" ? (payloadError as { providerStatus?: string }).providerStatus : response.status;
|
||||
error.category = payloadError && typeof payloadError === "object" ? (payloadError as { category?: string }).category : undefined;
|
||||
return error;
|
||||
}
|
||||
|
||||
function agentFailurePresentation(error, { result = null, traceId = null } = {}) {
|
||||
function agentFailurePresentation(error: unknown, { result = null, traceId = null }: { result?: unknown; traceId?: string | null } = {}): WorkbenchAugmentedError {
|
||||
const code = normalizeErrorCode(error?.code);
|
||||
const message = String(error?.message ?? result?.error?.message ?? "").trim();
|
||||
const structuredBlocker = error?.blocker ?? result?.error?.blocker ?? result?.blocker ?? null;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["DOM", "ES2022"],
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"allowJs": false,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
|
||||
Vendored
+87
-6
@@ -76,7 +76,7 @@ export interface ElMap {
|
||||
codeAgentProviderProfile: HTMLSelectElement
|
||||
codeAgentTimeout: HTMLSelectElement
|
||||
gatewayShellTimeout: HTMLSelectElement
|
||||
commandSend: HTMLElement
|
||||
commandSend: HTMLButtonElement
|
||||
commandClear: HTMLElement
|
||||
devicePodSelect: HTMLSelectElement
|
||||
devicePodStatusTag: HTMLElement
|
||||
@@ -114,11 +114,11 @@ export interface WorkbenchState {
|
||||
traceDetailsOpen: Map<string, boolean>;
|
||||
conversationRenderVersion: number;
|
||||
conversationScrollUserActiveUntil: number;
|
||||
conversationScrollPosition: { top: number; left: number };
|
||||
traceScrollPositions: Map<string, { top: number; left: number }>;
|
||||
conversationScrollPosition: { top: number; left: number; bottomGap?: number };
|
||||
traceScrollPositions: Map<string, { top: number; left: number; bottomGap?: number }>;
|
||||
traceScrollPinnedToBottom: Map<string, boolean>;
|
||||
traceScrollUserActiveUntil: Map<string, number>;
|
||||
traceBodyScrollPositions: Map<string, { top: number; left: number }>;
|
||||
traceBodyScrollPositions: Map<string, { top: number; left: number; bottomGap?: number }>;
|
||||
traceBodyScrollUserActiveUntil: Map<string, number>;
|
||||
traceProgrammaticScrollWrites: number;
|
||||
fullTraceReplayInFlight: Set<string>;
|
||||
@@ -369,7 +369,7 @@ declare global {
|
||||
function nonEmptyString(value: WorkbenchUnknown, fallback?: string | null): string
|
||||
function textOf(value: WorkbenchUnknown): string;
|
||||
function shortTime(iso: string): string;
|
||||
function textSpan(text: string, className?: string): HTMLElement;
|
||||
function textSpan(text: string, className?: string): HTMLButtonElement;
|
||||
function replaceChildren(parent: Node | null, ...children: Node[]): void;
|
||||
function firstNonEmptyString(...values: WorkbenchUnknown[]): string
|
||||
function toneClass(tone: string | null | undefined): string;
|
||||
@@ -540,7 +540,7 @@ declare global {
|
||||
function setTraceScrollTop(top: number): void;
|
||||
function setConversationScrollTop(top: number): void;
|
||||
function resetProgrammaticScrollWriteCount(): number;
|
||||
function recordField(name: string, value: WorkbenchUnknown): HTMLElement;
|
||||
function recordField(name: string, value: WorkbenchUnknown): HTMLButtonElement;
|
||||
const CODEX_RUNNER_CAPABLE_PROVIDERS: readonly string[];
|
||||
const UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN: RegExp;
|
||||
function openWorkbenchDialog(body: HTMLElement, title?: string): HTMLDialogElement;
|
||||
@@ -555,4 +555,85 @@ declare global {
|
||||
function updateGatewayShellTimeout(value: number): void;
|
||||
}
|
||||
|
||||
// ---- Workbench 内部对 Error 的扩展字段集合,由 agentErrorFromHttpResponse 等
|
||||
// 在 throw 之前直接挂到 Error 实例上。runtime-side 使用 Object.assign / 直接
|
||||
// 赋值;tsc 看到 Error 类型后无法访问自定义字段,所以走 WorkbenchAugmentedError
|
||||
// 把这些字段明示出来。----
|
||||
export interface WorkbenchAugmentedErrorFields {
|
||||
traceId?: string;
|
||||
code?: string;
|
||||
layer?: string;
|
||||
blocker?: string;
|
||||
retryable?: boolean;
|
||||
userMessage?: string;
|
||||
missingConfig?: string[];
|
||||
route?: string;
|
||||
toolName?: string;
|
||||
timeoutMs?: number;
|
||||
hardTimeoutMs?: number;
|
||||
idleMs?: number;
|
||||
lastActivityAt?: number;
|
||||
waitingFor?: string;
|
||||
providerStatus?: string;
|
||||
category?: string;
|
||||
[k: string]: WorkbenchUnknown | undefined;
|
||||
}
|
||||
|
||||
export interface WorkbenchAugmentedError extends Error, WorkbenchAugmentedErrorFields {}
|
||||
|
||||
// 常见 options / 配置 / 控制器参数的轻量接口
|
||||
export interface WorkbenchScrollOptions {
|
||||
deferred?: boolean;
|
||||
force?: boolean;
|
||||
behavior?: ScrollBehavior;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkbenchLoadOptions {
|
||||
quiet?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkbenchActionResultOptions {
|
||||
result?: unknown;
|
||||
traceId?: string;
|
||||
blockedError?: unknown;
|
||||
}
|
||||
|
||||
// 一些返回 summary 对象的 function 的统一形态
|
||||
export interface WorkbenchSummaryShape {
|
||||
tone: string;
|
||||
kind: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
capabilityLevel: string;
|
||||
lastTraceId: string;
|
||||
codeAgentStatus: string;
|
||||
rows: Array<{ label: string; value: string; tone: string }>;
|
||||
summary: string;
|
||||
missingFields: string[];
|
||||
}
|
||||
|
||||
export interface WorkbenchComposerShape {
|
||||
disabled: boolean;
|
||||
submitMode: "send" | "steer" | "retry" | "cancel";
|
||||
targetTraceId: string | null;
|
||||
traceId: string | null;
|
||||
conversationId: string | null;
|
||||
sessionId: string | null;
|
||||
blocked: boolean;
|
||||
tone: string;
|
||||
[k: string]: WorkbenchUnknown | undefined;
|
||||
}
|
||||
|
||||
// ---- ambient global aliases(让未 import 的 source 也能用) ----
|
||||
declare global {
|
||||
type WorkbenchAugmentedErrorG = WorkbenchAugmentedError;
|
||||
type WorkbenchAugmentedErrorFieldsG = WorkbenchAugmentedErrorFields;
|
||||
type WorkbenchScrollOptionsG = WorkbenchScrollOptions;
|
||||
type WorkbenchLoadOptionsG = WorkbenchLoadOptions;
|
||||
type WorkbenchActionResultOptionsG = WorkbenchActionResultOptions;
|
||||
type WorkbenchSummaryShapeG = WorkbenchSummaryShape;
|
||||
type WorkbenchComposerShapeG = WorkbenchComposerShape;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
Reference in New Issue
Block a user