From d92e78c9561289bd420f4f22178ba9875925fd70 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 16:31:13 +0900 Subject: [PATCH] refactor(sdk): narrow response fallbacks Replace unsafe unknown-array assertions with generic fallback types and explicit todo status narrowing. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/cli/run/poll-for-completion.ts | 28 +++++++++++-------- src/hooks/read-image-resizer/hook.ts | 4 +-- .../storage/messages-reader.ts | 2 +- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/cli/run/poll-for-completion.ts b/src/cli/run/poll-for-completion.ts index f393b5b9d..fe47a6e7f 100644 --- a/src/cli/run/poll-for-completion.ts +++ b/src/cli/run/poll-for-completion.ts @@ -2,7 +2,7 @@ import pc from "picocolors" import type { RunContext } from "./types" import type { EventState } from "./events" import { checkCompletionConditions } from "./completion" -import { normalizeSDKResponse } from "../../shared" +import { isRecord, normalizeSDKResponse } from "../../shared" const DEFAULT_POLL_INTERVAL_MS = 500 const DEFAULT_REQUIRED_CONSECUTIVE = 1 @@ -11,6 +11,17 @@ const MIN_STABILIZATION_MS = 1_000 const DEFAULT_EVENT_WATCHDOG_MS = 30_000 // 30 seconds const DEFAULT_SECONDARY_MEANINGFUL_WORK_TIMEOUT_MS = 60_000 // 60 seconds +type SessionStatusMap = Record + +function isIncompleteTodo(value: unknown): boolean { + if (!isRecord(value)) { + return true + } + + const status = value.status + return status !== "completed" && status !== "cancelled" +} + export interface PollOptions { pollIntervalMs?: number requiredConsecutive?: number @@ -123,22 +134,18 @@ export async function pollForCompletion( path: { id: ctx.sessionID }, query: { directory: ctx.directory }, }) - const children = normalizeSDKResponse(childrenRes, [] as unknown[]) + const children = normalizeSDKResponse(childrenRes, []) const todosRes = await ctx.client.session.todo({ path: { id: ctx.sessionID }, query: { directory: ctx.directory }, }) - const todos = normalizeSDKResponse(todosRes, [] as unknown[]) + const todos = normalizeSDKResponse(todosRes, []) const hasActiveChildren = Array.isArray(children) && children.length > 0 const hasActiveTodos = Array.isArray(todos) && - todos.some( - (t: unknown) => - (t as { status?: string })?.status !== "completed" && - (t as { status?: string })?.status !== "cancelled" - ) + todos.some(isIncompleteTodo) const hasActiveWork = hasActiveChildren || hasActiveTodos if (hasActiveWork) { @@ -189,10 +196,7 @@ async function getMainSessionStatus( const statusesRes = await ctx.client.session.status({ query: { directory: ctx.directory }, }) - const statuses = normalizeSDKResponse( - statusesRes, - {} as Record - ) + const statuses = normalizeSDKResponse(statusesRes, {}) if (!(ctx.sessionID in statuses)) { return "idle" } diff --git a/src/hooks/read-image-resizer/hook.ts b/src/hooks/read-image-resizer/hook.ts index a537dca87..56df0c189 100644 --- a/src/hooks/read-image-resizer/hook.ts +++ b/src/hooks/read-image-resizer/hook.ts @@ -189,8 +189,8 @@ export function createReadImageResizerHook(_ctx: PluginInput) { } } - if (attachmentsToRemove.length > 0) { - const rawAttachments = outputRecord.attachments as unknown[] + if (attachmentsToRemove.length > 0 && Array.isArray(outputRecord.attachments)) { + const rawAttachments = outputRecord.attachments for (const toRemove of attachmentsToRemove) { const removeIndex = rawAttachments.indexOf(toRemove) if (removeIndex !== -1) { diff --git a/src/hooks/session-recovery/storage/messages-reader.ts b/src/hooks/session-recovery/storage/messages-reader.ts index ecedf2400..094a1b03b 100644 --- a/src/hooks/session-recovery/storage/messages-reader.ts +++ b/src/hooks/session-recovery/storage/messages-reader.ts @@ -62,7 +62,7 @@ export async function readMessagesFromSDK( ): Promise { try { const response = await client.session.messages({ path: { id: sessionID } }) - const data = normalizeSDKResponse(response, [] as unknown[], { + const data = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true, }) if (!Array.isArray(data)) return []