refactor(session): centralize fallback title policy (#39890)
This commit is contained in:
parent
1311d909a7
commit
7aaf4e7750
27 changed files with 145 additions and 93 deletions
|
|
@ -76,7 +76,7 @@ import { PromptHistoryProvider } from "./component/prompt/history"
|
|||
import { FrecencyProvider } from "./component/prompt/frecency"
|
||||
import { PromptStashProvider } from "./component/prompt/stash"
|
||||
import { Toast, ToastProvider, useToast } from "./ui/toast"
|
||||
import { isDefaultTitle } from "./util/session"
|
||||
import { isFallbackTitle } from "@opencode-ai/util/session-title-fallback"
|
||||
import * as Model from "./util/model"
|
||||
import { ArgsProvider, useArgs, type Args } from "./context/args"
|
||||
import open from "open"
|
||||
|
|
@ -537,7 +537,7 @@ function App(props: { pair?: DialogPairCredentials }) {
|
|||
|
||||
if (route.data.type === "session") {
|
||||
const title = session?.title
|
||||
if (!title || isDefaultTitle(title)) {
|
||||
if (!title || isFallbackTitle(title)) {
|
||||
renderer.setTerminalTitle("OpenCode")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { abbreviateHome } from "../runtime"
|
|||
import { useTuiPaths } from "../context/runtime"
|
||||
import { truncateFilePath } from "../ui/file-path"
|
||||
import { stringWidth } from "../util/string-width"
|
||||
import { sessionTitle } from "../util/session"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
import { Spinner } from "./spinner"
|
||||
|
||||
const RECENT_LIMIT = 8
|
||||
|
|
@ -80,7 +80,7 @@ export function DialogOpen() {
|
|||
const name = project?.canonical === "/" ? undefined : project?.name || path.basename(project?.canonical ?? "")
|
||||
const running = data.session.family(session.id).some((id) => data.session.status(id) === "running")
|
||||
return {
|
||||
title: sessionTitle(session),
|
||||
title: withTimestampedFallback(session),
|
||||
value: { type: "session", sessionID: session.id } as OpenTarget,
|
||||
category: "Sessions",
|
||||
footer: `${name ? `${Locale.truncate(name, 20)} · ` : ""}${timeAgo(session.time.updated)}`,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import { Spinner } from "./spinner"
|
|||
import { errorMessage } from "../util/error"
|
||||
import { useSessionTabs } from "../context/session-tabs"
|
||||
import { useStorage } from "../context/storage"
|
||||
import { sessionTitle } from "../util/session"
|
||||
import { useConfig } from "../config"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
|
||||
export function DialogSessionList() {
|
||||
const dialog = useDialog()
|
||||
|
|
@ -82,7 +82,9 @@ export function DialogSessionList() {
|
|||
(session.projectID === current?.project.id && session.location.directory === current.directory),
|
||||
)
|
||||
if (!query) return sessions
|
||||
return sessions.filter((session) => !session.parentID && sessionTitle(session).toLowerCase().includes(query))
|
||||
return sessions.filter(
|
||||
(session) => !session.parentID && withTimestampedFallback(session).toLowerCase().includes(query),
|
||||
)
|
||||
})
|
||||
const sessions = createMemo(() => {
|
||||
const query = filter().trim()
|
||||
|
|
@ -144,7 +146,9 @@ export function DialogSessionList() {
|
|||
const slot = sessionTabs.enabled() ? undefined : slotByID.get(session.id)
|
||||
const deleting = toDelete() === session.id
|
||||
return {
|
||||
title: deleting ? `Press ${shortcuts.get("session.delete")} again to confirm` : sessionTitle(session),
|
||||
title: deleting
|
||||
? `Press ${shortcuts.get("session.delete")} again to confirm`
|
||||
: withTimestampedFallback(session),
|
||||
value: session.id,
|
||||
category,
|
||||
footer,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { isDeepEqual } from "remeda"
|
|||
import { createSimpleContext } from "./helper"
|
||||
import { useClient } from "./client"
|
||||
import { useData } from "./data"
|
||||
import { sessionTitle } from "../util/session"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
import { useEvent } from "./event"
|
||||
import { useRoute } from "./route"
|
||||
import { useConfig } from "../config"
|
||||
|
|
@ -116,7 +116,8 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
|
|||
const sessionID = root(route.data.sessionID)
|
||||
history = recordSessionTabHistory(history, sessionID)
|
||||
const session = data.session.get(sessionID)
|
||||
const title = session?.title ?? (newTab() ? NEW_SESSION_TAB_TITLE : session ? sessionTitle(session) : undefined)
|
||||
const title =
|
||||
session?.title ?? (newTab() ? NEW_SESSION_TAB_TITLE : session ? withTimestampedFallback(session) : undefined)
|
||||
const tabs = openSessionTab(state().tabs, { sessionID, title })
|
||||
if (tabs === state().tabs && !state().unread[sessionID]) return
|
||||
update((draft) => {
|
||||
|
|
@ -130,7 +131,10 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
|
|||
const next = state().tabs.reduce<SessionTab[]>((tabs, tab) => {
|
||||
const sessionID = root(tab.sessionID)
|
||||
const session = data.session.get(sessionID)
|
||||
return openSessionTab(tabs, { sessionID, title: session ? sessionTitle(session) : tab.title })
|
||||
return openSessionTab(tabs, {
|
||||
sessionID,
|
||||
title: session ? withTimestampedFallback(session) : tab.title,
|
||||
})
|
||||
}, [])
|
||||
const unread = Object.entries(state().unread).reduce<Record<string, SessionTabUnread>>((result, entry) => {
|
||||
const sessionID = root(entry[0])
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
// back to the usual two-press exit sequence through RunFooter.requestExit().
|
||||
import path from "path"
|
||||
import { CliRenderEvents, createCliRenderer, type CliRenderer, type ScrollbackWriter } from "@opentui/core"
|
||||
import { isDefaultTitle } from "../util/session"
|
||||
import { isFallbackTitle } from "@opencode-ai/util/session-title-fallback"
|
||||
import { monoSnapshot } from "./mono"
|
||||
import { entrySplash, exitSplash, splashMeta } from "./splash"
|
||||
import { resolveRunTheme } from "./theme"
|
||||
|
|
@ -105,7 +105,7 @@ function shutdown(renderer: CliRenderer): void {
|
|||
}
|
||||
|
||||
function splashInfo(title: string | undefined, history: RunPrompt[]) {
|
||||
if (title && !isDefaultTitle(title)) {
|
||||
if (title && !isFallbackTitle(title)) {
|
||||
return {
|
||||
title,
|
||||
showSession: true,
|
||||
|
|
@ -176,7 +176,7 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
|
|||
if (mono) renderer.on(CliRenderEvents.EXTERNAL_OUTPUT, monoSnapshot)
|
||||
const setTitle = (title?: string) => {
|
||||
if (input.host.platform !== "linux") return
|
||||
if (!title || isDefaultTitle(title)) return renderer.setTerminalTitle("OpenCode")
|
||||
if (!title || isFallbackTitle(title)) return renderer.setTerminalTitle("OpenCode")
|
||||
renderer.setTerminalTitle(`OC | ${title.length > 40 ? title.slice(0, 37) + "..." : title}`)
|
||||
}
|
||||
setTitle(input.sessionTitle)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { useTheme } from "../../../context/theme"
|
|||
import { Locale } from "../../../util/locale"
|
||||
import { Keymap } from "../../../context/keymap"
|
||||
import { useComposerTab } from "./index"
|
||||
import { sessionTitle } from "../../../util/session"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
|
||||
interface SubagentEntry {
|
||||
sessionID: string
|
||||
|
|
@ -39,7 +39,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
if (current.parentID) {
|
||||
const siblings = data.session.list().filter((s) => s.parentID === current.parentID)
|
||||
for (const sibling of siblings) {
|
||||
const title = sessionTitle(sibling)
|
||||
const title = withTimestampedFallback(sibling)
|
||||
const agentMatch = title.match(/@(\w+) subagent/)
|
||||
const agent = sibling.agent
|
||||
? Locale.titlecase(sibling.agent)
|
||||
|
|
@ -58,7 +58,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
} else {
|
||||
const children = data.session.list().filter((s) => s.parentID === props.sessionID)
|
||||
for (const child of children) {
|
||||
const title = sessionTitle(child)
|
||||
const title = withTimestampedFallback(child)
|
||||
const agentMatch = title.match(/@(\w+) subagent/)
|
||||
const agent = child.agent
|
||||
? Locale.titlecase(child.agent)
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ import { switchLabel } from "../../util/model"
|
|||
import { findMessageBoundary, messageNavigationSlack } from "./message-navigation"
|
||||
import { stringWidth } from "../../util/string-width"
|
||||
import { useArgs } from "../../context/args"
|
||||
import { sessionTitle } from "../../util/session"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
|
|
@ -3308,7 +3308,7 @@ function formatSessionTranscript(session: SessionInfo, messages: SessionMessageI
|
|||
})
|
||||
return [`## Assistant\n\n${content.join("\n\n")}`]
|
||||
})
|
||||
return `# ${sessionTitle(session)}\n\n**Session ID:** ${session.id}\n**Created:** ${new Date(session.time.created).toLocaleString()}\n**Updated:** ${new Date(session.time.updated).toLocaleString()}\n\n---\n\n${body.join("\n\n---\n\n")}\n`
|
||||
return `# ${withTimestampedFallback(session)}\n\n**Session ID:** ${session.id}\n**Created:** ${new Date(session.time.created).toLocaleString()}\n**Updated:** ${new Date(session.time.updated).toLocaleString()}\n\n---\n\n${body.join("\n\n---\n\n")}\n`
|
||||
}
|
||||
|
||||
export function parseApplyPatchFiles(value: unknown) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { createMemo, Show } from "solid-js"
|
|||
import { useTheme } from "../../context/theme"
|
||||
import { useConfig } from "../../config"
|
||||
import { PluginSlot } from "../../plugin/context"
|
||||
import { sessionTitle } from "../../util/session"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
|
||||
import { getScrollAcceleration } from "../../util/scroll"
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|||
<box flexShrink={0} gap={1} paddingRight={1}>
|
||||
<box paddingRight={1}>
|
||||
<text fg={theme.text.default}>
|
||||
<b>{sessionTitle(session()!)}</b>
|
||||
<b>{withTimestampedFallback(session()!)}</b>
|
||||
</text>
|
||||
<Show when={session()!.location.workspaceID}>
|
||||
<text fg={theme.text.subdued}>{session()!.location.workspaceID}</text>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
import type { ModelInfo, SessionInfo, SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client"
|
||||
import type { ModelInfo, SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client"
|
||||
import { Locale } from "./locale"
|
||||
|
||||
export function isDefaultTitle(title?: string) {
|
||||
return (
|
||||
title === undefined || /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(title)
|
||||
)
|
||||
}
|
||||
|
||||
export function sessionTitle(session: Pick<SessionInfo, "parentID" | "time" | "title">) {
|
||||
return (
|
||||
session.title ?? `${session.parentID ? "Child" : "New"} session - ${new Date(session.time.created).toISOString()}`
|
||||
)
|
||||
}
|
||||
|
||||
export function lastAssistantWithUsage(messages: ReadonlyArray<SessionMessageInfo>, boundary?: string) {
|
||||
const boundaryIndex = boundary ? messages.findIndex((message) => message.id === boundary) : -1
|
||||
if (boundary && boundaryIndex === -1) return undefined
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue