diff --git a/src/server/__tests__/conversation-service.test.ts b/src/server/__tests__/conversation-service.test.ts index 108c143a..d95edeb0 100644 --- a/src/server/__tests__/conversation-service.test.ts +++ b/src/server/__tests__/conversation-service.test.ts @@ -486,6 +486,7 @@ describe('ConversationService', () => { expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('kimi-k2.6') expect(env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST).toBe('1') expect(env.CLAUDE_CODE_ATTRIBUTION_HEADER).toBe('0') + expect(env.CC_HAHA_TRANSCRIPT_ENTRYPOINT).toBe('claude-desktop') expect(env.CLAUDE_CODE_ENTRYPOINT).toBeUndefined() expect(env.CC_HAHA_TRACE_PROVIDER_ID).toBeUndefined() expect(env.CC_HAHA_TRACE_PROVIDER_NAME).toBeUndefined() diff --git a/src/server/services/conversationService.ts b/src/server/services/conversationService.ts index 7688e19a..1776e332 100644 --- a/src/server/services/conversationService.ts +++ b/src/server/services/conversationService.ts @@ -1206,6 +1206,9 @@ export class ConversationService { // should come from Desktop-managed config or inherited launch env, not // be reintroduced from the repo's .env file. CC_HAHA_SKIP_DOTENV: '1', + // Keep the SDK runtime identity for auth and client behavior, but stamp + // desktop-owned transcripts with an entrypoint visible to Claude /resume. + CC_HAHA_TRANSCRIPT_ENTRYPOINT: 'claude-desktop', ...(explicitProviderEnv ? { CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: '1' } : {}), diff --git a/src/utils/__tests__/sessionStorageFlush.test.ts b/src/utils/__tests__/sessionStorageFlush.test.ts index 9f2615a4..be39e8f8 100644 --- a/src/utils/__tests__/sessionStorageFlush.test.ts +++ b/src/utils/__tests__/sessionStorageFlush.test.ts @@ -6,11 +6,18 @@ import * as path from 'node:path' import { enqueueSessionEntryAfterPendingForTesting, flushSessionStorage, + getTranscriptPathForSession, + recordTranscript, resetProjectForTesting, } from '../sessionStorage.js' +import { switchSession } from '../../bootstrap/state.js' +import type { SessionId } from '../../types/ids.js' import type { CustomTitleMessage } from '../../types/logs.js' const originalConfigDir = process.env.CLAUDE_CONFIG_DIR +const originalTranscriptEntrypoint = process.env.CC_HAHA_TRANSCRIPT_ENTRYPOINT +const originalEntrypoint = process.env.CLAUDE_CODE_ENTRYPOINT +const originalTestPersistence = process.env.TEST_ENABLE_SESSION_PERSISTENCE async function createTmpDir(): Promise { const dir = path.join( @@ -27,6 +34,7 @@ describe('sessionStorage flush', () => { beforeEach(async () => { tmpDir = await createTmpDir() process.env.CLAUDE_CONFIG_DIR = tmpDir + process.env.TEST_ENABLE_SESSION_PERSISTENCE = '1' resetProjectForTesting() }) @@ -37,9 +45,49 @@ describe('sessionStorage flush', () => { } else { process.env.CLAUDE_CONFIG_DIR = originalConfigDir } + if (originalTranscriptEntrypoint === undefined) { + delete process.env.CC_HAHA_TRANSCRIPT_ENTRYPOINT + } else { + process.env.CC_HAHA_TRANSCRIPT_ENTRYPOINT = originalTranscriptEntrypoint + } + if (originalEntrypoint === undefined) { + delete process.env.CLAUDE_CODE_ENTRYPOINT + } else { + process.env.CLAUDE_CODE_ENTRYPOINT = originalEntrypoint + } + if (originalTestPersistence === undefined) { + delete process.env.TEST_ENABLE_SESSION_PERSISTENCE + } else { + process.env.TEST_ENABLE_SESSION_PERSISTENCE = originalTestPersistence + } await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {}) }) + it('records the desktop transcript entrypoint without changing the runtime entrypoint', async () => { + const sessionId = '22222222-2222-4222-8222-222222222222' + switchSession(sessionId as SessionId) + process.env.CLAUDE_CODE_ENTRYPOINT = 'sdk-cli' + process.env.CC_HAHA_TRANSCRIPT_ENTRYPOINT = 'claude-desktop' + resetProjectForTesting() + + await recordTranscript([{ + type: 'user', + uuid: '33333333-3333-4333-8333-333333333333', + message: { role: 'user', content: 'desktop resume visibility' }, + } as never]) + await flushSessionStorage() + + const transcript = await fs.readFile(getTranscriptPathForSession(sessionId), 'utf-8') + const userEntry = transcript + .trim() + .split('\n') + .map((line) => JSON.parse(line) as Record) + .find((entry) => entry.type === 'user') + + expect(userEntry?.entrypoint).toBe('claude-desktop') + expect(process.env.CLAUDE_CODE_ENTRYPOINT).toBe('sdk-cli') + }) + it('drains writes that are queued by pending operations during flush', async () => { const transcriptPath = path.join(tmpDir, 'late-enqueue.jsonl') const entry: CustomTitleMessage = { diff --git a/src/utils/sessionStorage.ts b/src/utils/sessionStorage.ts index dad2db70..5dd7e941 100644 --- a/src/utils/sessionStorage.ts +++ b/src/utils/sessionStorage.ts @@ -421,7 +421,10 @@ export function getUserType(): string { } function getEntrypoint(): string | undefined { - return process.env.CLAUDE_CODE_ENTRYPOINT + return ( + process.env.CC_HAHA_TRANSCRIPT_ENTRYPOINT?.trim() || + process.env.CLAUDE_CODE_ENTRYPOINT + ) } export function isCustomTitleEnabled(): boolean {