name a live opencode row from the prompt until opencode names it
Same shape as the claude side, which has never shown a live row without a name. OpenCode titles a session from the conversation and does it well, but asynchronously — so for the whole time a turn is RUNNING, which is exactly what /chat/live shows, the session is still called "New session - <ISO>". Its own title wins the moment it exists; until then the row falls back to the prompt that started the session. Kept per sessionKey, first turn only, so it stays the name of the conversation rather than following whatever was asked most recently. Dropped with the session id it sits beside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
|||||||
renameOpenCodeSession,
|
renameOpenCodeSession,
|
||||||
isOpenCodeSessionId,
|
isOpenCodeSessionId,
|
||||||
} from './opencode-sessions';
|
} from './opencode-sessions';
|
||||||
import { getOpenCodeSession } from './opencode/state';
|
import { getOpenCodePrompt, getOpenCodeSession } from './opencode/state';
|
||||||
import { listChatModels } from './list-models';
|
import { listChatModels } from './list-models';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import { readSttConfig } from '../server-settings/stt';
|
import { readSttConfig } from '../server-settings/stt';
|
||||||
@@ -150,11 +150,12 @@ chatRouter.get('/live', async (ctx) => {
|
|||||||
isGenerating: true,
|
isGenerating: true,
|
||||||
pendingTasks: 0,
|
pendingTasks: 0,
|
||||||
harness: 'opencode' as const,
|
harness: 'opencode' as const,
|
||||||
// Null for the first seconds of a turn (the id is only known once the subprocess prints it), and
|
// OpenCode's own title wins as soon as it exists — it is derived from the conversation and is
|
||||||
// null again while OpenCode still calls the session `New session - <ISO>` — `listOpenCodeSessions`
|
// better than anything we would compose. Until then (and it titles asynchronously, so "until
|
||||||
// normalises that placeholder to `(untitled)`, and a live row is better blank than named after a
|
// then" covers the whole time a turn is RUNNING, which is exactly what this panel shows) fall
|
||||||
// timestamp. The real title arrives on a later poll, because OpenCode writes it asynchronously.
|
// back to the prompt that started the session. Same shape as the Claude side, which has never
|
||||||
title: meta?.title && meta.title !== '(untitled)' ? meta.title : null,
|
// shown a live row without a name.
|
||||||
|
title: meta?.title && meta.title !== '(untitled)' ? meta.title : (getOpenCodePrompt(sessionKey) ?? null),
|
||||||
cwd: meta?.cwd || null,
|
cwd: meta?.cwd || null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,4 +12,25 @@ export const setOpenCodeSession = (sessionKey: string, opencodeSessionId: string
|
|||||||
|
|
||||||
export const clearOpenCodeSession = (sessionKey: string): void => {
|
export const clearOpenCodeSession = (sessionKey: string): void => {
|
||||||
sessionKeyToOpenCode.delete(sessionKey);
|
sessionKeyToOpenCode.delete(sessionKey);
|
||||||
|
sessionKeyToPrompt.delete(sessionKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// What the user asked, kept only to name a session OpenCode has not named yet.
|
||||||
|
//
|
||||||
|
// OpenCode titles a session from the conversation, and does it well — but asynchronously, so a RUNNING
|
||||||
|
// turn is called `New session - <ISO>`, which is precisely when `/chat/live` displays it. This is the
|
||||||
|
// stand-in for that window: shown while the real title is still the placeholder, and dropped the moment
|
||||||
|
// OpenCode publishes its own, which is always the better one.
|
||||||
|
//
|
||||||
|
// Set on the FIRST turn of a session only, so it stays the name of the conversation rather than
|
||||||
|
// following whatever was asked most recently.
|
||||||
|
const sessionKeyToPrompt = new Map<string, string>();
|
||||||
|
|
||||||
|
export const rememberOpenCodePrompt = (sessionKey: string, prompt: string): void => {
|
||||||
|
if (sessionKeyToPrompt.has(sessionKey)) return;
|
||||||
|
const oneLine = prompt.replace(/\s+/g, ' ').trim();
|
||||||
|
if (!oneLine) return;
|
||||||
|
sessionKeyToPrompt.set(sessionKey, oneLine.length > 60 ? `${oneLine.slice(0, 59)}…` : oneLine);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOpenCodePrompt = (sessionKey: string): string | undefined => sessionKeyToPrompt.get(sessionKey);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
UserSession,
|
UserSession,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { sessionManager } from './session-manager';
|
import { sessionManager } from './session-manager';
|
||||||
|
import { rememberOpenCodePrompt } from './opencode/state';
|
||||||
import { sendClaudeCodeStreaming } from '@@/channels/send-claude-code';
|
import { sendClaudeCodeStreaming } from '@@/channels/send-claude-code';
|
||||||
import { sendOpenCodeStreaming } from '@@/channels/send-opencode';
|
import { sendOpenCodeStreaming } from '@@/channels/send-opencode';
|
||||||
import { ensureGeneralChatSessionsCwd } from './claude-sessions';
|
import { ensureGeneralChatSessionsCwd } from './claude-sessions';
|
||||||
@@ -407,6 +408,9 @@ async function handleOpenCodeChat(
|
|||||||
|
|
||||||
const cwd = await resolveChatCwd(msg, email, userId);
|
const cwd = await resolveChatCwd(msg, email, userId);
|
||||||
|
|
||||||
|
// Names this session in the Live panel until OpenCode gets round to titling it. First turn only.
|
||||||
|
rememberOpenCodePrompt(sessionId, msg.displayText || msg.prompt);
|
||||||
|
|
||||||
const groupSlug = msg.groupSlug || null;
|
const groupSlug = msg.groupSlug || null;
|
||||||
|
|
||||||
const session = sessionManager.getOrCreate(sessionId, email, cwd, model, groupSlug, msg.context, msg.contextId);
|
const session = sessionManager.getOrCreate(sessionId, email, cwd, model, groupSlug, msg.context, msg.contextId);
|
||||||
|
|||||||
Reference in New Issue
Block a user