diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index d825ec8e1..173480c45 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -207,6 +207,36 @@ function simplifyUnionVariants(params: { obj: Record; variants: return { variants: stripped ? nonNullVariants : variants }; } +function flattenUnionFallback( + obj: Record, + value: unknown, +): Record | undefined { + if (!Array.isArray(value)) { + return undefined; + } + const variants = (value as Record[]).filter((v) => v && typeof v === "object"); + const types = new Set(variants.map((v) => v.type).filter(Boolean)); + if (variants.length === 1) { + const merged: Record = { ...variants[0] }; + copySchemaMeta(obj, merged); + return merged; + } + if (types.size === 1) { + const merged: Record = { type: Array.from(types)[0] }; + copySchemaMeta(obj, merged); + return merged; + } + const first = variants[0]; + if (first?.type) { + const merged: Record = { type: first.type }; + copySchemaMeta(obj, merged); + return merged; + } + const merged: Record = {}; + copySchemaMeta(obj, merged); + return merged; +} + function cleanSchemaForGeminiWithDefs( schema: unknown, defs: SchemaDefs | undefined, @@ -343,58 +373,14 @@ function cleanSchemaForGeminiWithDefs( // If simplifyUnionVariants couldn't reduce the union above, flatten it // here as a fallback: pick the first variant's type or use a permissive // schema so the tool declaration is accepted. - if (cleaned.anyOf && Array.isArray(cleaned.anyOf)) { - const variants = (cleaned.anyOf as Record[]).filter( - (v) => v && typeof v === "object", - ); - const types = new Set(variants.map((v) => v.type).filter(Boolean)); - if (variants.length === 1) { - const merged: Record = { ...variants[0] }; - copySchemaMeta(cleaned, merged); - return merged; - } - if (types.size === 1) { - const merged: Record = { type: Array.from(types)[0] }; - copySchemaMeta(cleaned, merged); - return merged; - } - // Mixed types (e.g. string | array): use first variant's type. - // The execute function already handles type coercion at runtime. - const first = variants[0]; - if (first?.type) { - const merged: Record = { type: first.type }; - copySchemaMeta(cleaned, merged); - return merged; - } - const merged: Record = {}; - copySchemaMeta(cleaned, merged); - return merged; + const flattenedAnyOf = flattenUnionFallback(cleaned, cleaned.anyOf); + if (flattenedAnyOf) { + return flattenedAnyOf; } - if (cleaned.oneOf && Array.isArray(cleaned.oneOf)) { - const variants = (cleaned.oneOf as Record[]).filter( - (v) => v && typeof v === "object", - ); - const types = new Set(variants.map((v) => v.type).filter(Boolean)); - if (variants.length === 1) { - const merged: Record = { ...variants[0] }; - copySchemaMeta(cleaned, merged); - return merged; - } - if (types.size === 1) { - const merged: Record = { type: Array.from(types)[0] }; - copySchemaMeta(cleaned, merged); - return merged; - } - const first = variants[0]; - if (first?.type) { - const merged: Record = { type: first.type }; - copySchemaMeta(cleaned, merged); - return merged; - } - const merged: Record = {}; - copySchemaMeta(cleaned, merged); - return merged; + const flattenedOneOf = flattenUnionFallback(cleaned, cleaned.oneOf); + if (flattenedOneOf) { + return flattenedOneOf; } return cleaned; diff --git a/src/agents/tool-display-common.ts b/src/agents/tool-display-common.ts index 0cf72efc6..28fcb0045 100644 --- a/src/agents/tool-display-common.ts +++ b/src/agents/tool-display-common.ts @@ -483,7 +483,10 @@ function unwrapShellWrapper(command: string): string { return inner ? (stripOuterQuotes(inner) ?? command) : command; } -function firstTopLevelStage(command: string): string { +function scanTopLevelChars( + command: string, + visit: (char: string, index: number) => boolean | void, +): void { let quote: '"' | "'" | undefined; let escaped = false; @@ -511,52 +514,39 @@ function firstTopLevelStage(command: string): string { continue; } - if (char === ";") { - return command.slice(0, i); - } - if ((char === "&" || char === "|") && command[i + 1] === char) { - return command.slice(0, i); + if (visit(char, i) === false) { + return; } } +} - return command; +function firstTopLevelStage(command: string): string { + let splitIndex = -1; + scanTopLevelChars(command, (char, index) => { + if (char === ";") { + splitIndex = index; + return false; + } + if ((char === "&" || char === "|") && command[index + 1] === char) { + splitIndex = index; + return false; + } + return true; + }); + return splitIndex >= 0 ? command.slice(0, splitIndex) : command; } function splitTopLevelPipes(command: string): string[] { const parts: string[] = []; - let quote: '"' | "'" | undefined; - let escaped = false; let start = 0; - for (let i = 0; i < command.length; i += 1) { - const char = command[i]; - - if (escaped) { - escaped = false; - continue; + scanTopLevelChars(command, (char, index) => { + if (char === "|" && command[index - 1] !== "|" && command[index + 1] !== "|") { + parts.push(command.slice(start, index)); + start = index + 1; } - if (char === "\\") { - escaped = true; - continue; - } - - if (quote) { - if (char === quote) { - quote = undefined; - } - continue; - } - - if (char === '"' || char === "'") { - quote = char; - continue; - } - - if (char === "|" && command[i - 1] !== "|" && command[i + 1] !== "|") { - parts.push(command.slice(start, i)); - start = i + 1; - } - } + return true; + }); parts.push(command.slice(start)); return parts.map((part) => part.trim()).filter((part) => part.length > 0); diff --git a/src/slack/monitor/events/interactions.ts b/src/slack/monitor/events/interactions.ts index a865f8452..8e936d45d 100644 --- a/src/slack/monitor/events/interactions.ts +++ b/src/slack/monitor/events/interactions.ts @@ -18,11 +18,9 @@ type SelectOption = { text?: { text?: string }; }; -type InteractionSummary = { - interactionType?: "block_action" | "view_submission" | "view_closed"; - actionId: string; - blockId?: string; +type InteractionSelectionFields = { actionType?: string; + blockId?: string; inputKind?: "text" | "number" | "email" | "url" | "rich_text"; value?: string; selectedValues?: string[]; @@ -39,6 +37,11 @@ type InteractionSummary = { inputUrl?: string; richTextValue?: unknown; richTextPreview?: string; +}; + +type InteractionSummary = InteractionSelectionFields & { + interactionType?: "block_action" | "view_submission" | "view_closed"; + actionId: string; userId?: string; teamId?: string; triggerId?: string; @@ -50,26 +53,9 @@ type InteractionSummary = { threadTs?: string; }; -type ModalInputSummary = { +type ModalInputSummary = InteractionSelectionFields & { blockId: string; actionId: string; - actionType?: string; - inputKind?: "text" | "number" | "email" | "url" | "rich_text"; - value?: string; - selectedValues?: string[]; - selectedUsers?: string[]; - selectedChannels?: string[]; - selectedConversations?: string[]; - selectedLabels?: string[]; - selectedDate?: string; - selectedTime?: string; - selectedDateTime?: number; - inputValue?: string; - inputNumber?: number; - inputEmail?: string; - inputUrl?: string; - richTextValue?: unknown; - richTextPreview?: string; }; function readOptionValues(options: unknown): string[] | undefined {