cc-haha/desktop/src/lib/parseRunOutput.ts
程序员阿江(Relakkes) 983d83f86b feat: enhance scheduled tasks with flexible scheduling, run now, execution logs, and edit support
- Add 7 frequency modes (every N min/hours, daily, weekdays, specific days, monthly, custom cron) with progressive disclosure UI
- Add "Run Now" button with confirmation popover and fire-and-forget API
- Add execution logs panel (TaskRunsPanel) with auto-polling and accordion behavior
- Add task edit mode with cron reverse-parsing (parseCron) to populate form
- Add server-side extractAssistantText to store meaningful AI responses instead of raw NDJSON
- Fix session linking: pass --session-id to CLI subprocess so "View conversation" navigates to actual content
- Fix MACRO undefined error by adding --preload to Bun.spawn
- Add confirmation popovers for all destructive actions (run/disable/delete)
- Add DayOfWeekPicker component for specific-days scheduling
- Add cronDescribe utility with i18n support and unit tests
- Display task creation time and last run time
- Add ~50 i18n keys (en/zh) for all new UI elements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:40:13 +08:00

80 lines
2.3 KiB
TypeScript

/**
* Parse task run output into displayable text.
*
* The output may be in one of two formats:
*
* 1. **Extracted text** (new runs) — The server's `extractAssistantText` has
* already parsed the raw NDJSON and stored only the AI's text response.
* This is plain text / markdown that should be returned as-is.
*
* 2. **Raw NDJSON** (old runs before the server-side extraction was added) —
* Each line is a JSON object from the CLI's stream-json output. We parse
* these and extract assistant text blocks + result messages.
*
* Detection: if at least one line parses as JSON with a recognized `type`
* field, treat as NDJSON. Otherwise return as-is.
*/
export function parseRunOutput(raw: string): string {
if (!raw || !raw.trim()) return ''
const lines = raw.trim().split('\n')
// Quick check: does this look like NDJSON? (first non-empty line starts with '{')
const firstLine = lines.find((l) => l.trim())
if (!firstLine || !firstLine.trim().startsWith('{')) {
// Already extracted plain text — return as-is
return raw.trim()
}
// Try to parse as NDJSON (legacy format)
const textParts: string[] = []
let anyRecognized = false
for (const line of lines) {
if (!line.trim()) continue
let parsed: any
try {
parsed = JSON.parse(line)
} catch {
continue
}
const type = parsed?.type
if (type === 'assistant') {
anyRecognized = true
const content = parsed?.message?.content
if (!Array.isArray(content)) continue
for (const block of content) {
if (block.type === 'text' && block.text?.trim()) {
textParts.push(block.text.trim())
}
}
}
if (type === 'result') {
anyRecognized = true
const result = parsed?.result
if (typeof result === 'string' && result.trim()) {
textParts.push(result.trim())
} else if (result?.message?.trim()) {
textParts.push(result.message.trim())
}
}
if (type === 'system' || type === 'user') {
anyRecognized = true
// Skip these — not useful to display
}
}
// If we recognized NDJSON structure, return extracted text
if (anyRecognized) {
return textParts.join('\n\n')
}
// Fallback: the JSON lines didn't have recognized types — return raw
return raw.trim()
}