mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
fix(server): expose desktop sessions to Claude resume #988
Stamp desktop-owned transcripts with the claude-desktop entrypoint while preserving the SDK runtime entrypoint used for provider authentication. Tests: focused regression 41/41; chat contract 240/240; server 1622/1622; coverage 5/5 with changed lines 4/4. Live-path proof: hermetic loopback transcript was visible in Claude Code 2.1.206 /resume; no real user credentials or config were used. Constraint: check:impact remains policy-blocked until a PR has the allow-cli-core-change label. Confidence: high Scope-risk: narrow
This commit is contained in:
parent
d6cd0f1fc7
commit
936d970751
@ -486,6 +486,7 @@ describe('ConversationService', () => {
|
|||||||
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('kimi-k2.6')
|
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('kimi-k2.6')
|
||||||
expect(env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST).toBe('1')
|
expect(env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST).toBe('1')
|
||||||
expect(env.CLAUDE_CODE_ATTRIBUTION_HEADER).toBe('0')
|
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.CLAUDE_CODE_ENTRYPOINT).toBeUndefined()
|
||||||
expect(env.CC_HAHA_TRACE_PROVIDER_ID).toBeUndefined()
|
expect(env.CC_HAHA_TRACE_PROVIDER_ID).toBeUndefined()
|
||||||
expect(env.CC_HAHA_TRACE_PROVIDER_NAME).toBeUndefined()
|
expect(env.CC_HAHA_TRACE_PROVIDER_NAME).toBeUndefined()
|
||||||
|
|||||||
@ -1206,6 +1206,9 @@ export class ConversationService {
|
|||||||
// should come from Desktop-managed config or inherited launch env, not
|
// should come from Desktop-managed config or inherited launch env, not
|
||||||
// be reintroduced from the repo's .env file.
|
// be reintroduced from the repo's .env file.
|
||||||
CC_HAHA_SKIP_DOTENV: '1',
|
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
|
...(explicitProviderEnv
|
||||||
? { CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: '1' }
|
? { CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: '1' }
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
@ -6,11 +6,18 @@ import * as path from 'node:path'
|
|||||||
import {
|
import {
|
||||||
enqueueSessionEntryAfterPendingForTesting,
|
enqueueSessionEntryAfterPendingForTesting,
|
||||||
flushSessionStorage,
|
flushSessionStorage,
|
||||||
|
getTranscriptPathForSession,
|
||||||
|
recordTranscript,
|
||||||
resetProjectForTesting,
|
resetProjectForTesting,
|
||||||
} from '../sessionStorage.js'
|
} from '../sessionStorage.js'
|
||||||
|
import { switchSession } from '../../bootstrap/state.js'
|
||||||
|
import type { SessionId } from '../../types/ids.js'
|
||||||
import type { CustomTitleMessage } from '../../types/logs.js'
|
import type { CustomTitleMessage } from '../../types/logs.js'
|
||||||
|
|
||||||
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR
|
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<string> {
|
async function createTmpDir(): Promise<string> {
|
||||||
const dir = path.join(
|
const dir = path.join(
|
||||||
@ -27,6 +34,7 @@ describe('sessionStorage flush', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await createTmpDir()
|
tmpDir = await createTmpDir()
|
||||||
process.env.CLAUDE_CONFIG_DIR = tmpDir
|
process.env.CLAUDE_CONFIG_DIR = tmpDir
|
||||||
|
process.env.TEST_ENABLE_SESSION_PERSISTENCE = '1'
|
||||||
resetProjectForTesting()
|
resetProjectForTesting()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -37,9 +45,49 @@ describe('sessionStorage flush', () => {
|
|||||||
} else {
|
} else {
|
||||||
process.env.CLAUDE_CONFIG_DIR = originalConfigDir
|
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(() => {})
|
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<string, unknown>)
|
||||||
|
.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 () => {
|
it('drains writes that are queued by pending operations during flush', async () => {
|
||||||
const transcriptPath = path.join(tmpDir, 'late-enqueue.jsonl')
|
const transcriptPath = path.join(tmpDir, 'late-enqueue.jsonl')
|
||||||
const entry: CustomTitleMessage = {
|
const entry: CustomTitleMessage = {
|
||||||
|
|||||||
@ -421,7 +421,10 @@ export function getUserType(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEntrypoint(): string | undefined {
|
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 {
|
export function isCustomTitleEnabled(): boolean {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user