Bin29's picture
Sync from main: e764154 feat(plot-skill): add math-exam-diagram SKILL.md for exam-style math figures
abcf568
import { InMemoryStudioEventBus } from '../../../events/event-bus'
import { logPlotStudioTiming, readRunElapsedMs } from '../../../observability/plot-studio-timing'
import { extractLatestAssistantText, cancelRunState, failRunState, finalizeRunState } from '../session-runner-helpers'
import type {
StudioAssistantMessage,
StudioEventBus,
StudioRun,
StudioSession
} from '../../../domain/types'
import type { StudioSessionRunnerDependencies } from './dependency-center'
import type { StudioSubagentRunResult } from '../../tools/tool-runtime-context'
export async function handleCancelledRun(
deps: StudioSessionRunnerDependencies,
input: {
session: StudioSession
run: StudioRun
reason: string
},
): Promise<never> {
const cancelledRun = cancelRunState(input.run, input.reason)
await deps.runStore?.update(input.run.id, cancelledRun)
;(deps.sharedEventBus ?? new InMemoryStudioEventBus()).publish({
type: 'run_updated',
run: cancelledRun
})
logPlotStudioTiming(input.session.studioKind, 'run.failed', {
sessionId: input.session.id,
runId: input.run.id,
error: input.reason,
cancelled: true,
runElapsedMs: readRunElapsedMs(cancelledRun),
}, 'warn')
throw new Error(input.reason)
}
export async function finalizeSuccessfulRun(
deps: StudioSessionRunnerDependencies,
input: {
session: StudioSession
run: StudioRun
assistantMessage: StudioAssistantMessage
outcome: 'continue' | 'stop' | 'compact'
eventBus: StudioEventBus
},
): Promise<StudioSubagentRunResult & { run: StudioRun; assistantMessage: StudioAssistantMessage }> {
const finishedRun = finalizeRunState({ run: input.run, outcome: input.outcome })
await deps.runStore?.update(input.run.id, finishedRun)
input.eventBus.publish({
type: 'run_updated',
run: finishedRun
})
const finalAssistantMessage = await findLatestAssistantMessage(
deps,
input.session.id,
input.assistantMessage,
)
logPlotStudioTiming(input.session.studioKind, 'run.completed', {
sessionId: input.session.id,
runId: input.run.id,
outcome: input.outcome,
eventCount: input.eventBus.list().length,
runElapsedMs: readRunElapsedMs(finishedRun),
})
return {
run: finishedRun,
assistantMessage: finalAssistantMessage,
text: extractLatestAssistantText(finalAssistantMessage.parts)
}
}
export async function handleFailedRun(
deps: StudioSessionRunnerDependencies,
input: {
session: StudioSession
run: StudioRun
error: unknown
},
): Promise<never> {
const message = input.error instanceof Error ? input.error.message : String(input.error)
const failedRun = failRunState(input.run, message)
await deps.runStore?.update(input.run.id, failedRun)
;(deps.sharedEventBus ?? new InMemoryStudioEventBus()).publish({
type: 'run_updated',
run: failedRun
})
logPlotStudioTiming(input.session.studioKind, 'run.failed', {
sessionId: input.session.id,
runId: input.run.id,
error: message,
runElapsedMs: readRunElapsedMs(failedRun),
}, 'warn')
throw input.error
}
async function findLatestAssistantMessage(
deps: StudioSessionRunnerDependencies,
sessionId: string,
fallback: StudioAssistantMessage,
): Promise<StudioAssistantMessage> {
const messages = await deps.messageStore.listBySessionId(sessionId)
const latestAssistantMessage = [...messages]
.reverse()
.find((message): message is StudioAssistantMessage => message.role === 'assistant')
return latestAssistantMessage ?? fallback
}