init claude-code
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { queryHaiku } from '../../services/api/claude.js'
|
||||
import type { Message } from '../../types/message.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { errorMessage } from '../../utils/errors.js'
|
||||
import { safeParseJSON } from '../../utils/json.js'
|
||||
import { extractTextContent } from '../../utils/messages.js'
|
||||
import { extractConversationText } from '../../utils/sessionTitle.js'
|
||||
import { asSystemPrompt } from '../../utils/systemPromptType.js'
|
||||
|
||||
export async function generateSessionName(
|
||||
messages: Message[],
|
||||
signal: AbortSignal,
|
||||
): Promise<string | null> {
|
||||
const conversationText = extractConversationText(messages)
|
||||
if (!conversationText) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await queryHaiku({
|
||||
systemPrompt: asSystemPrompt([
|
||||
'Generate a short kebab-case name (2-4 words) that captures the main topic of this conversation. Use lowercase words separated by hyphens. Examples: "fix-login-bug", "add-auth-feature", "refactor-api-client", "debug-test-failures". Return JSON with a "name" field.',
|
||||
]),
|
||||
userPrompt: conversationText,
|
||||
outputFormat: {
|
||||
type: 'json_schema',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
signal,
|
||||
options: {
|
||||
querySource: 'rename_generate_name',
|
||||
agents: [],
|
||||
isNonInteractiveSession: false,
|
||||
hasAppendSystemPrompt: false,
|
||||
mcpTools: [],
|
||||
},
|
||||
})
|
||||
|
||||
const content = extractTextContent(result.message.content)
|
||||
|
||||
const response = safeParseJSON(content)
|
||||
if (
|
||||
response &&
|
||||
typeof response === 'object' &&
|
||||
'name' in response &&
|
||||
typeof (response as { name: unknown }).name === 'string'
|
||||
) {
|
||||
return (response as { name: string }).name
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
// Haiku timeout/rate-limit/network are expected operational failures —
|
||||
// logForDebugging, not logError. Called automatically on every 3rd bridge
|
||||
// message (initReplBridge.ts), so errors here would flood the error file.
|
||||
logForDebugging(`generateSessionName failed: ${errorMessage(error)}`, {
|
||||
level: 'error',
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Command } from '../../commands.js'
|
||||
|
||||
const rename = {
|
||||
type: 'local-jsx',
|
||||
name: 'rename',
|
||||
description: 'Rename the current conversation',
|
||||
immediate: true,
|
||||
argumentHint: '[name]',
|
||||
load: () => import('./rename.js'),
|
||||
} satisfies Command
|
||||
|
||||
export default rename
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { UUID } from 'crypto'
|
||||
import { getSessionId } from '../../bootstrap/state.js'
|
||||
import {
|
||||
getBridgeBaseUrlOverride,
|
||||
getBridgeTokenOverride,
|
||||
} from '../../bridge/bridgeConfig.js'
|
||||
import type { ToolUseContext } from '../../Tool.js'
|
||||
import type {
|
||||
LocalJSXCommandContext,
|
||||
LocalJSXCommandOnDone,
|
||||
} from '../../types/command.js'
|
||||
import { getMessagesAfterCompactBoundary } from '../../utils/messages.js'
|
||||
import {
|
||||
getTranscriptPath,
|
||||
saveAgentName,
|
||||
saveCustomTitle,
|
||||
} from '../../utils/sessionStorage.js'
|
||||
import { isTeammate } from '../../utils/teammate.js'
|
||||
import { generateSessionName } from './generateSessionName.js'
|
||||
|
||||
export async function call(
|
||||
onDone: LocalJSXCommandOnDone,
|
||||
context: ToolUseContext & LocalJSXCommandContext,
|
||||
args: string,
|
||||
): Promise<null> {
|
||||
// Prevent teammates from renaming - their names are set by team leader
|
||||
if (isTeammate()) {
|
||||
onDone(
|
||||
'Cannot rename: This session is a swarm teammate. Teammate names are set by the team leader.',
|
||||
{ display: 'system' },
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
let newName: string
|
||||
if (!args || args.trim() === '') {
|
||||
const generated = await generateSessionName(
|
||||
getMessagesAfterCompactBoundary(context.messages),
|
||||
context.abortController.signal,
|
||||
)
|
||||
if (!generated) {
|
||||
onDone(
|
||||
'Could not generate a name: no conversation context yet. Usage: /rename <name>',
|
||||
{ display: 'system' },
|
||||
)
|
||||
return null
|
||||
}
|
||||
newName = generated
|
||||
} else {
|
||||
newName = args.trim()
|
||||
}
|
||||
|
||||
const sessionId = getSessionId() as UUID
|
||||
const fullPath = getTranscriptPath()
|
||||
|
||||
// Always save the custom title (session name)
|
||||
await saveCustomTitle(sessionId, newName, fullPath)
|
||||
|
||||
// Sync title to bridge session on claude.ai/code (best-effort, non-blocking).
|
||||
// v2 env-less bridge stores cse_* in replBridgeSessionId —
|
||||
// updateBridgeSessionTitle retags internally for the compat endpoint.
|
||||
const appState = context.getAppState()
|
||||
const bridgeSessionId = appState.replBridgeSessionId
|
||||
if (bridgeSessionId) {
|
||||
const tokenOverride = getBridgeTokenOverride()
|
||||
void import('../../bridge/createSession.js').then(
|
||||
({ updateBridgeSessionTitle }) =>
|
||||
updateBridgeSessionTitle(bridgeSessionId, newName, {
|
||||
baseUrl: getBridgeBaseUrlOverride(),
|
||||
getAccessToken: tokenOverride ? () => tokenOverride : undefined,
|
||||
}).catch(() => {}),
|
||||
)
|
||||
}
|
||||
|
||||
// Also persist as the session's agent name for prompt-bar display
|
||||
await saveAgentName(sessionId, newName, fullPath)
|
||||
context.setAppState(prev => ({
|
||||
...prev,
|
||||
standaloneAgentContext: {
|
||||
...prev.standaloneAgentContext,
|
||||
name: newName,
|
||||
},
|
||||
}))
|
||||
|
||||
onDone(`Session renamed to: ${newName}`, { display: 'system' })
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user