From 2992621354c212bdf900500e65d230d916dd7927 Mon Sep 17 00:00:00 2001 From: murashit Date: Tue, 23 Jun 2026 20:21:10 +0900 Subject: [PATCH] Centralize chat domain derivations --- src/domain/pending-requests/model.ts | 9 +++++ src/domain/threads/title-generation-model.ts | 2 +- src/domain/threads/title.ts | 2 +- .../chat/app-server/actions/threads.ts | 2 +- .../runtime/thread-settings-update.ts | 24 ++++++------- .../application/runtime/settings-actions.ts | 2 +- .../chat/application/state/message-stream.ts | 35 ++++--------------- .../message-stream/factories/goal-items.ts | 2 +- .../message-stream/format/permission-rows.ts | 2 +- .../chat/domain/message-stream/selectors.ts | 15 ++++++++ .../message-stream/semantics/active-turn.ts | 2 +- .../chat/domain/message-stream/updates.ts | 26 ++++++++------ .../domain/pending-requests/result-items.ts | 2 +- .../surface/message-stream-projection.ts | 9 ++--- .../presentation/message-stream/view-model.ts | 8 ++--- .../pending-requests/view-model.ts | 19 ++++------ src/shared/id/thread-id.ts | 3 ++ src/shared/object/defined-prop.ts | 6 ++++ src/shared/text/preview.ts | 11 ++++++ src/utils.ts | 25 ++----------- tests/runtime/runtime-settings.test.ts | 2 +- 21 files changed, 102 insertions(+), 106 deletions(-) rename src/features/chat/{application => app-server}/runtime/thread-settings-update.ts (100%) create mode 100644 src/shared/id/thread-id.ts create mode 100644 src/shared/object/defined-prop.ts create mode 100644 src/shared/text/preview.ts diff --git a/src/domain/pending-requests/model.ts b/src/domain/pending-requests/model.ts index 9c27fbe7..7bd5414a 100644 --- a/src/domain/pending-requests/model.ts +++ b/src/domain/pending-requests/model.ts @@ -147,6 +147,15 @@ export function approvalActionKind(action: ApprovalAction): "accept" | "accept-s return typeof action === "object" ? action.intent : action; } +export function defaultPendingApprovalOptions(): PendingApprovalOption[] { + return [ + { id: "accept", label: "Allow", action: "accept", intent: "accept" }, + { id: "accept-session", label: "Allow session", action: "accept-session", intent: "accept-session" }, + { id: "decline", label: "Deny", action: "decline", intent: "decline" }, + { id: "cancel", label: "Cancel", action: "cancel", intent: "cancel" }, + ]; +} + export function questionDefaultAnswer(question: PendingUserInputQuestion): string { return question.options?.[0]?.label ?? ""; } diff --git a/src/domain/threads/title-generation-model.ts b/src/domain/threads/title-generation-model.ts index d0ab02f0..10e0edcb 100644 --- a/src/domain/threads/title-generation-model.ts +++ b/src/domain/threads/title-generation-model.ts @@ -1,5 +1,5 @@ import type { ThreadConversationSummary } from "./transcript"; -import { truncate } from "../../utils"; +import { truncate } from "../../shared/text/preview"; const THREAD_TITLE_CONTEXT_MAX_CHARS = 4_000; const DEFAULT_CONTEXT_PAGE_LIMIT = 20; diff --git a/src/domain/threads/title.ts b/src/domain/threads/title.ts index 1069bec7..89541848 100644 --- a/src/domain/threads/title.ts +++ b/src/domain/threads/title.ts @@ -1,4 +1,4 @@ -import { shortThreadId } from "../../utils"; +import { shortThreadId } from "../../shared/id/thread-id"; import type { Thread } from "./model"; const MAX_ARCHIVED_THREAD_DISPLAY_TITLE_LENGTH = 96; diff --git a/src/features/chat/app-server/actions/threads.ts b/src/features/chat/app-server/actions/threads.ts index 2d73bcc0..85b6d280 100644 --- a/src/features/chat/app-server/actions/threads.ts +++ b/src/features/chat/app-server/actions/threads.ts @@ -3,7 +3,7 @@ import type { ThreadCatalogEvent } from "../../../../workspace/thread-catalog"; import { threadActivationSnapshotFromAppServerResponse } from "../../../../app-server/threads"; import type { RuntimeSnapshot } from "../../domain/runtime/snapshot"; import { runtimeConfigOrDefault } from "../../domain/runtime/effective"; -import { serviceTierRequestForThreadStart } from "../../application/runtime/thread-settings-update"; +import { serviceTierRequestForThreadStart } from "../runtime/thread-settings-update"; import { resumedThreadAction } from "../../application/state/actions"; import { captureChatServerActionClientScope, type ChatServerActionHost } from "./host"; import type { ChatState } from "../../application/state/root-reducer"; diff --git a/src/features/chat/application/runtime/thread-settings-update.ts b/src/features/chat/app-server/runtime/thread-settings-update.ts similarity index 100% rename from src/features/chat/application/runtime/thread-settings-update.ts rename to src/features/chat/app-server/runtime/thread-settings-update.ts index 28b86bee..df89440f 100644 --- a/src/features/chat/application/runtime/thread-settings-update.ts +++ b/src/features/chat/app-server/runtime/thread-settings-update.ts @@ -4,10 +4,10 @@ import { type RuntimeServiceTierRequest, type RuntimeSettingsPatch, } from "../../../../domain/runtime/thread-settings"; -import { currentModel, currentReasoningEffort, fastRuntimeServiceTierRequestValue } from "../../domain/runtime/effective"; import type { RuntimeConfigSnapshot } from "../../../../domain/runtime/config"; -import type { RuntimeSnapshot } from "../../domain/runtime/snapshot"; +import { currentModel, currentReasoningEffort, fastRuntimeServiceTierRequestValue } from "../../domain/runtime/effective"; import { effectiveCollaborationMode, type PendingRuntimeIntent } from "../../domain/runtime/intent"; +import type { RuntimeSnapshot } from "../../domain/runtime/snapshot"; type TurnCollaborationModeWarning = "missing-model"; @@ -34,16 +34,6 @@ export function serviceTierRequestForThreadStart(snapshot: RuntimeSnapshot, conf return runtimeSettingsPatchValue(serviceTierTransportIntent(snapshot, config, "thread-start")); } -function requestedTurnCollaborationModeSettings(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): TurnCollaborationModeSettings { - const model = currentModel(snapshot, config); - const effort = currentReasoningEffort(snapshot, config); - if (!model) return { collaborationMode: null, warning: "missing-model" }; - return { - collaborationMode: runtimeCollaborationModeSettings(snapshot.selectedCollaborationMode, model, effort), - warning: null, - }; -} - export function pendingRuntimeSettingsPatch(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): PendingRuntimeSettingsPatch { const update: RuntimeSettingsPatch = {}; const runtimeCollaborationModeSettings = requestedTurnCollaborationModeSettings(snapshot, config); @@ -79,6 +69,16 @@ export function pendingRuntimeSettingsPatch(snapshot: RuntimeSnapshot, config: R return { update, collaborationModeWarning: null }; } +function requestedTurnCollaborationModeSettings(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): TurnCollaborationModeSettings { + const model = currentModel(snapshot, config); + const effort = currentReasoningEffort(snapshot, config); + if (!model) return { collaborationMode: null, warning: "missing-model" }; + return { + collaborationMode: runtimeCollaborationModeSettings(snapshot.selectedCollaborationMode, model, effort), + warning: null, + }; +} + function runtimeTransportIntentFromPending(intent: PendingRuntimeIntent): RuntimeTransportIntent { if (intent.kind === "set") return { kind: "set", value: intent.value }; if (intent.kind === "resetToConfig") return { kind: "clear" }; diff --git a/src/features/chat/application/runtime/settings-actions.ts b/src/features/chat/application/runtime/settings-actions.ts index 7fca9705..8808db81 100644 --- a/src/features/chat/application/runtime/settings-actions.ts +++ b/src/features/chat/application/runtime/settings-actions.ts @@ -7,7 +7,7 @@ import { autoReviewActive, fastModeActive, runtimeConfigOrDefault } from "../../ import { pendingRuntimeSettingsPatch as buildPendingRuntimeSettingsPatch, type PendingRuntimeSettingsPatch, -} from "./thread-settings-update"; +} from "../../app-server/runtime/thread-settings-update"; import type { RuntimeSnapshot } from "../../domain/runtime/snapshot"; import { nextCollaborationMode, type CollaborationModeSelection, type RequestedFastMode } from "../../domain/runtime/intent"; import type { ChatAction, ChatState } from "../state/root-reducer"; diff --git a/src/features/chat/application/state/message-stream.ts b/src/features/chat/application/state/message-stream.ts index c580e37c..784555c6 100644 --- a/src/features/chat/application/state/message-stream.ts +++ b/src/features/chat/application/state/message-stream.ts @@ -1,4 +1,4 @@ -import { upsertMessageStreamItemById } from "../../domain/message-stream/updates"; +import { completeReasoningItems, upsertMessageStreamItemById } from "../../domain/message-stream/updates"; import type { MessageStreamItem, MessageStreamMessageItem } from "../../domain/message-stream/items"; import { normalizeProposedPlanMarkdown } from "../../domain/message-stream/format/proposed-plan"; import { messageStreamSemanticClassifications } from "../../domain/message-stream/semantics/classify"; @@ -402,44 +402,23 @@ function appendItemOutputToMessageStream( } function completeReasoningInMessageStream(state: ChatMessageStreamState, turnId: string): ChatMessageStreamState { - const stableUpdate = completedReasoningItems(state.stableItems, turnId); + const stableItems = completeReasoningItems(state.stableItems, turnId); const activeSegment = state.activeSegment; if (activeSegment?.turnId !== turnId) { - return stableUpdate.changed ? patchObject(state, { stableItems: stableUpdate.items }) : state; + return stableItems !== state.stableItems ? patchObject(state, { stableItems }) : state; } - const activeUpdate = completedReasoningItems(activeSegment.items, turnId); + const activeItems = completeReasoningItems(activeSegment.items, turnId); - return stableUpdate.changed || activeUpdate.changed + return stableItems !== state.stableItems || activeItems !== activeSegment.items ? patchObject(state, { - stableItems: stableUpdate.items, - activeSegment: activeUpdate.changed ? activeSegmentFromItems(activeSegment.turnId, activeUpdate.items) : activeSegment, + stableItems, + activeSegment: activeItems !== activeSegment.items ? activeSegmentFromItems(activeSegment.turnId, activeItems) : activeSegment, }) : state; } -function completedReasoningItems( - items: readonly MessageStreamItem[], - turnId: string, -): { items: readonly MessageStreamItem[]; changed: boolean } { - let changed = false; - const nextItems: MessageStreamItem[] = []; - for (const item of items) { - if (item.kind !== "reasoning" || item.turnId !== turnId) { - nextItems.push(item); - } else { - changed = true; - nextItems.push({ - ...item, - status: "completed", - executionState: "completed", - } satisfies MessageStreamItem); - } - } - return { items: changed ? nextItems : items, changed }; -} - function updateActiveSegment( state: ChatMessageStreamState, turnId: string, diff --git a/src/features/chat/domain/message-stream/factories/goal-items.ts b/src/features/chat/domain/message-stream/factories/goal-items.ts index 605d7ddc..d8df0412 100644 --- a/src/features/chat/domain/message-stream/factories/goal-items.ts +++ b/src/features/chat/domain/message-stream/factories/goal-items.ts @@ -1,5 +1,5 @@ import type { ThreadGoal, ThreadGoalStatus } from "../../../../../domain/threads/goal"; -import { truncate } from "../../../../../utils"; +import { truncate } from "../../../../../shared/text/preview"; import type { GoalMessageStreamItem } from "../items"; const GOAL_SUMMARY_LIMIT = 140; diff --git a/src/features/chat/domain/message-stream/format/permission-rows.ts b/src/features/chat/domain/message-stream/format/permission-rows.ts index 4d5a6502..695f9847 100644 --- a/src/features/chat/domain/message-stream/format/permission-rows.ts +++ b/src/features/chat/domain/message-stream/format/permission-rows.ts @@ -1,4 +1,4 @@ -import { jsonPreview } from "../../../../../utils"; +import { jsonPreview } from "../../../../../shared/text/preview"; interface DetailRow { key: string; diff --git a/src/features/chat/domain/message-stream/selectors.ts b/src/features/chat/domain/message-stream/selectors.ts index cf23bff5..03da2068 100644 --- a/src/features/chat/domain/message-stream/selectors.ts +++ b/src/features/chat/domain/message-stream/selectors.ts @@ -10,6 +10,21 @@ export interface PlanImplementationTarget { itemId: string; } +export interface MessageStreamItemsEmptySource { + items: readonly MessageStreamItem[]; + stableItems?: readonly MessageStreamItem[] | undefined; + activeItems?: readonly MessageStreamItem[] | undefined; +} + +export function messageStreamItemsEmpty(source: MessageStreamItemsEmptySource): boolean { + if (!source.stableItems && !source.activeItems) return source.items.length === 0; + return messageStreamSegmentsEmpty(source.stableItems ?? [], source.activeItems ?? []); +} + +export function messageStreamSegmentsEmpty(stableItems: readonly MessageStreamItem[], activeItems: readonly MessageStreamItem[]): boolean { + return stableItems.length === 0 && activeItems.length === 0; +} + export function forkCandidatesFromItems(items: readonly MessageStreamItem[]): readonly ForkCandidate[] { const turnOutcomeItemsByTurn = new Map(); for (const { item, capabilities } of messageStreamSemanticClassifications(items)) { diff --git a/src/features/chat/domain/message-stream/semantics/active-turn.ts b/src/features/chat/domain/message-stream/semantics/active-turn.ts index 935b77ad..523889e6 100644 --- a/src/features/chat/domain/message-stream/semantics/active-turn.ts +++ b/src/features/chat/domain/message-stream/semantics/active-turn.ts @@ -1,4 +1,4 @@ -import { truncate } from "../../../../../utils"; +import { truncate } from "../../../../../shared/text/preview"; import { collabAgentStateExecutionState } from "../agent-state"; import type { AgentRunSummary, diff --git a/src/features/chat/domain/message-stream/updates.ts b/src/features/chat/domain/message-stream/updates.ts index d0a3f383..2935bf17 100644 --- a/src/features/chat/domain/message-stream/updates.ts +++ b/src/features/chat/domain/message-stream/updates.ts @@ -29,16 +29,22 @@ function mergeChanges(previous: MessageStreamItem, next: MessageStreamItem): rea return nextChanges && nextChanges.length > 0 ? nextChanges : previousChanges; } -export function completeReasoningItems(items: readonly MessageStreamItem[], turnId: string): MessageStreamItem[] { - return items.map((item) => - item.kind === "reasoning" && item.turnId === turnId - ? { - ...item, - status: "completed", - executionState: "completed", - } - : item, - ); +export function completeReasoningItems(items: readonly MessageStreamItem[], turnId: string): readonly MessageStreamItem[] { + let changed = false; + const nextItems: MessageStreamItem[] = []; + for (const item of items) { + if (item.kind !== "reasoning" || item.turnId !== turnId) { + nextItems.push(item); + continue; + } + changed = true; + nextItems.push({ + ...item, + status: "completed", + executionState: "completed", + } satisfies MessageStreamItem); + } + return changed ? nextItems : items; } export function attachHookRunsToTurn( diff --git a/src/features/chat/domain/pending-requests/result-items.ts b/src/features/chat/domain/pending-requests/result-items.ts index 36bb2cbc..abe7eb55 100644 --- a/src/features/chat/domain/pending-requests/result-items.ts +++ b/src/features/chat/domain/pending-requests/result-items.ts @@ -8,7 +8,7 @@ import { type PendingUserInput, } from "../../../../domain/pending-requests/model"; import type { MessageStreamItem, MessageStreamUserInputQuestionResult } from "../message-stream/items"; -import { definedProp } from "../../../../utils"; +import { definedProp } from "../../../../shared/object/defined-prop"; export function createApprovalResultItem(approval: PendingApproval, action: ApprovalAction): MessageStreamItem { const kind = approvalActionKind(action); diff --git a/src/features/chat/panel/surface/message-stream-projection.ts b/src/features/chat/panel/surface/message-stream-projection.ts index 916ac759..581646f5 100644 --- a/src/features/chat/panel/surface/message-stream-projection.ts +++ b/src/features/chat/panel/surface/message-stream-projection.ts @@ -1,7 +1,6 @@ import type { ChatAction, ChatDisclosureBucket, ChatDisclosureUiState } from "../../application/state/root-reducer"; -import type { MessageStreamItem } from "../../domain/message-stream/items"; import { messageStreamViewBlocks, type MessageStreamViewBlock } from "../../presentation/message-stream/view-model"; -import { type ForkCandidate, type PlanImplementationTarget } from "../../domain/message-stream/selectors"; +import { messageStreamSegmentsEmpty, type ForkCandidate, type PlanImplementationTarget } from "../../domain/message-stream/selectors"; import type { MessageStreamRollbackCandidate } from "../../application/state/message-stream"; import type { MessageStreamContext } from "../../ui/message-stream/context"; import type { ChatPanelMessageStreamShellState } from "../shell-state"; @@ -142,7 +141,7 @@ function messageStreamStateProjection( state.forkCandidates, state.implementPlanTarget, ); - const pendingRequests = messageStreamBlockItemsEmpty(state.stableItems, state.activeItems) ? null : pendingRequestBlockFromState(state); + const pendingRequests = messageStreamSegmentsEmpty(state.stableItems, state.activeItems) ? null : pendingRequestBlockFromState(state); return { activeThreadId: state.activeThreadId, @@ -208,7 +207,3 @@ function pendingRequestBlockFromState( snapshot: pendingRequestBlockSnapshotFromState(pendingRequestBlockStateFromChatState(state)), }; } - -function messageStreamBlockItemsEmpty(stableItems: readonly MessageStreamItem[], activeItems: readonly MessageStreamItem[]): boolean { - return stableItems.length === 0 && activeItems.length === 0; -} diff --git a/src/features/chat/presentation/message-stream/view-model.ts b/src/features/chat/presentation/message-stream/view-model.ts index e74870e4..dad4bc4c 100644 --- a/src/features/chat/presentation/message-stream/view-model.ts +++ b/src/features/chat/presentation/message-stream/view-model.ts @@ -1,5 +1,6 @@ import type { MessageStreamSemanticClassification } from "../../domain/message-stream/semantics/types"; import type { AgentRunSummary, MessageStreamItem, TaskProgressMessageStreamItem } from "../../domain/message-stream/items"; +import { messageStreamItemsEmpty } from "../../domain/message-stream/selectors"; import { activeTurnLiveItems, messageStreamItemsWithoutActiveTaskProgress } from "../../domain/message-stream/semantics/active-turn"; import { messageStreamLayoutBlocks, type MessageStreamItemAnnotations, type MessageStreamLayoutBlock } from "./layout"; import { detailView, type DetailView } from "./detail-view"; @@ -132,7 +133,7 @@ export function messageStreamViewBlocks(input: MessageStreamPresentationBlockInp function messageStreamPresentationBlocks(input: MessageStreamPresentationBlockInput): MessageStreamPresentationBlock[] { const headerBlocks = historyPresentationBlocks(input); - if (messageStreamBlockItemsEmpty(input)) { + if (messageStreamItemsEmpty(input)) { return [...headerBlocks, { kind: "empty", key: "empty" }]; } @@ -180,11 +181,6 @@ function pendingRequestPresentationBlocks(input: MessageStreamPresentationBlockI ]; } -function messageStreamBlockItemsEmpty(input: MessageStreamPresentationBlockInput): boolean { - if (!input.stableItems && !input.activeItems) return input.items.length === 0; - return (input.stableItems?.length ?? 0) === 0 && (input.activeItems?.length ?? 0) === 0; -} - function layoutBlocksForInput(input: MessageStreamPresentationBlockInput): MessageStreamLayoutBlock[] { const { activeTurnId } = input; if (!activeTurnId || !input.stableItems || !input.activeItems) { diff --git a/src/features/chat/presentation/pending-requests/view-model.ts b/src/features/chat/presentation/pending-requests/view-model.ts index 0a47126f..1ef78923 100644 --- a/src/features/chat/presentation/pending-requests/view-model.ts +++ b/src/features/chat/presentation/pending-requests/view-model.ts @@ -1,5 +1,6 @@ import { approvalActionKind, + defaultPendingApprovalOptions, type ApprovalAction, type PendingApproval, type PendingMcpElicitation, @@ -136,13 +137,16 @@ function pendingApprovalViewModel(approval: PendingApproval): PendingApprovalVie function approvalActionOptions(approval: PendingApproval): ApprovalActionOption[] { const options = approval.actionOptions; - if (!options || options.length === 0) return defaultApprovalActionOptions(); - return options.map((option) => ({ + return (options && options.length > 0 ? options : defaultPendingApprovalOptions()).map(approvalActionOptionViewModel); +} + +function approvalActionOptionViewModel(option: { id: string; label: string; action: ApprovalAction }): ApprovalActionOption { + return { id: option.id, label: option.label, action: option.action, className: approvalActionClassName(option.action), - })); + }; } function pendingUserInputViewModel(input: PendingUserInput): PendingUserInputViewModel { @@ -164,15 +168,6 @@ function pendingUserInputViewModel(input: PendingUserInput): PendingUserInputVie }; } -function defaultApprovalActionOptions(): ApprovalActionOption[] { - return [ - { id: "accept", label: "Allow", action: "accept", className: "mod-cta" }, - { id: "accept-session", label: "Allow session", action: "accept-session", className: "" }, - { id: "decline", label: "Deny", action: "decline", className: "mod-warning" }, - { id: "cancel", label: "Cancel", action: "cancel", className: "" }, - ]; -} - function approvalActionClassName(action: ApprovalAction): string { const kind = approvalActionKind(action); if (kind === "accept") return "mod-cta"; diff --git a/src/shared/id/thread-id.ts b/src/shared/id/thread-id.ts new file mode 100644 index 00000000..1d47d1eb --- /dev/null +++ b/src/shared/id/thread-id.ts @@ -0,0 +1,3 @@ +export function shortThreadId(threadId: string): string { + return threadId.length <= 8 ? threadId : threadId.slice(0, 8); +} diff --git a/src/shared/object/defined-prop.ts b/src/shared/object/defined-prop.ts new file mode 100644 index 00000000..a7c5d3f4 --- /dev/null +++ b/src/shared/object/defined-prop.ts @@ -0,0 +1,6 @@ +export function definedProp( + key: Key, + value: Value | null | undefined, +): Record | Record { + return value === null || value === undefined ? {} : ({ [key]: value } as Record); +} diff --git a/src/shared/text/preview.ts b/src/shared/text/preview.ts new file mode 100644 index 00000000..ea80e9c9 --- /dev/null +++ b/src/shared/text/preview.ts @@ -0,0 +1,11 @@ +export function truncate(value: string, maxLength: number): string { + return value.length > maxLength ? `${value.slice(0, maxLength - 1)}...` : value; +} + +export function jsonPreview(value: unknown): string { + try { + return JSON.stringify(value, null, 2); + } catch { + return String(value); + } +} diff --git a/src/utils.ts b/src/utils.ts index 7422b221..8be83458 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,26 +1,7 @@ -export function truncate(value: string, maxLength: number): string { - return value.length > maxLength ? `${value.slice(0, maxLength - 1)}...` : value; -} - -export function jsonPreview(value: unknown): string { - try { - return JSON.stringify(value, null, 2); - } catch { - return String(value); - } -} - -export function definedProp( - key: Key, - value: Value | null | undefined, -): Record | Record { - return value === null || value === undefined ? {} : ({ [key]: value } as Record); -} +export { definedProp } from "./shared/object/defined-prop"; +export { jsonPreview, truncate } from "./shared/text/preview"; +export { shortThreadId } from "./shared/id/thread-id"; export function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } - -export function shortThreadId(threadId: string): string { - return threadId.length <= 8 ? threadId : threadId.slice(0, 8); -} diff --git a/tests/runtime/runtime-settings.test.ts b/tests/runtime/runtime-settings.test.ts index 24d9b1ac..c9302f96 100644 --- a/tests/runtime/runtime-settings.test.ts +++ b/tests/runtime/runtime-settings.test.ts @@ -23,7 +23,7 @@ import { resetRuntimeIntentToConfig, setRuntimeIntentValue } from "../../src/fea import { pendingRuntimeSettingsPatch, serviceTierRequestForThreadStart, -} from "../../src/features/chat/application/runtime/thread-settings-update"; +} from "../../src/features/chat/app-server/runtime/thread-settings-update"; import { contextSummary, rateLimitSummary } from "../../src/features/chat/presentation/runtime/status"; describe("runtime settings", () => {