cc-haha/src/server/__tests__/fixtures/mock-sdk-cli.ts
程序员阿江(Relakkes) 993b96cd39 Stabilize the desktop transcript so long agent sessions stay readable
The desktop app now keeps the composer stable while turns are active,
reduces low-signal tool noise in the transcript, restores project context
under the composer after session creation, and relies on the CLI's own
permission requests instead of injecting broader desktop-side Bash asks.

This also brings in the supporting desktop app source tree and the server
routes/session metadata needed for git info, filesystem browsing, session
resume, slash commands, and SDK-backed permission bridging so the UI can
operate as a coherent feature instead of a partial patch.

Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs
Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts
Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model
Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history
Confidence: medium
Scope-risk: broad
Reversibility: messy
Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual visual regression against the exact screenshots in a live desktop session
Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)
2026-04-06 20:37:44 +08:00

113 lines
2.9 KiB
TypeScript

const args = process.argv.slice(2)
function getArg(name: string): string | undefined {
const index = args.indexOf(name)
return index >= 0 ? args[index + 1] : undefined
}
function emit(ws: WebSocket, payload: Record<string, unknown>) {
ws.send(JSON.stringify(payload) + '\n')
}
function extractUserText(message: any): string {
const content = message?.message?.content
if (!Array.isArray(content)) return ''
return content
.filter((block: any) => block?.type === 'text' && typeof block.text === 'string')
.map((block: any) => block.text)
.join(' ')
}
const sdkUrl = getArg('--sdk-url')
const sessionId = getArg('--session-id') || crypto.randomUUID()
if (!sdkUrl) {
console.error('Missing --sdk-url')
process.exit(1)
}
const ws = new WebSocket(sdkUrl)
ws.addEventListener('open', () => {
emit(ws, {
type: 'system',
subtype: 'init',
model: 'mock-opus',
slash_commands: [{ name: 'help', description: 'Show help' }],
session_id: sessionId,
})
})
ws.addEventListener('message', (event) => {
const payload = typeof event.data === 'string' ? event.data : String(event.data)
const lines = payload.split('\n').map(line => line.trim()).filter(Boolean)
for (const line of lines) {
const parsed = JSON.parse(line)
if (parsed.type === 'user') {
const text = extractUserText(parsed)
emit(ws, {
type: 'stream_event',
event: { type: 'message_start' },
session_id: sessionId,
})
emit(ws, {
type: 'stream_event',
event: {
type: 'content_block_start',
index: 0,
content_block: { type: 'text', text: '' },
},
session_id: sessionId,
})
emit(ws, {
type: 'stream_event',
event: {
type: 'content_block_delta',
index: 0,
delta: { type: 'thinking_delta', thinking: 'Mock thinking...' },
},
session_id: sessionId,
})
emit(ws, {
type: 'stream_event',
event: {
type: 'content_block_delta',
index: 0,
delta: { type: 'text_delta', text: `Echo: ${text}` },
},
session_id: sessionId,
})
emit(ws, {
type: 'stream_event',
event: { type: 'content_block_stop', index: 0 },
session_id: sessionId,
})
emit(ws, {
type: 'result',
subtype: 'success',
is_error: false,
result: `Echo: ${text}`,
usage: { input_tokens: 3, output_tokens: 2 },
session_id: sessionId,
})
}
if (parsed.type === 'control_request' && parsed.request?.subtype === 'interrupt') {
emit(ws, {
type: 'result',
subtype: 'success',
is_error: false,
result: 'Interrupted',
usage: { input_tokens: 0, output_tokens: 0 },
session_id: sessionId,
})
}
}
})
ws.addEventListener('close', () => {
process.exit(0)
})