fix: unify company runtime recovery lifecycle

This commit is contained in:
LZH-YS1998
2026-07-14 14:35:43 +08:00
parent 5e02364eb4
commit b8202bbe9e
56 changed files with 8753 additions and 3542 deletions
@@ -676,7 +676,6 @@ export function mapBackendSession(raw: any): Session {
runtimeControlState: raw.runtime_control_state ?? raw.runtimeControlState,
canStop: raw.can_stop ?? raw.canStop,
canResume: raw.can_resume ?? raw.canResume,
resumeParentTaskId: raw.resume_parent_task_id ?? raw.resumeParentTaskId,
resumeParentSessionId: raw.resume_parent_session_id ?? raw.resumeParentSessionId,
pendingRuntimeCheckpointId: raw.pending_runtime_checkpoint_id ?? raw.pendingRuntimeCheckpointId,
stopIntentId: raw.stop_intent_id ?? raw.stopIntentId,
@@ -387,7 +387,6 @@ export function getConversationSessionView(
runtimeControlState: runtimeSource.runtimeControlState ?? normalizedActiveSession.runtimeControlState,
canStop: runtimeSource.canStop ?? normalizedActiveSession.canStop,
canResume: runtimeSource.canResume ?? normalizedActiveSession.canResume,
resumeParentTaskId: runtimeSource.resumeParentTaskId ?? normalizedActiveSession.resumeParentTaskId,
resumeParentSessionId: runtimeSource.resumeParentSessionId ?? normalizedActiveSession.resumeParentSessionId,
pendingRuntimeCheckpointId: runtimeSource.pendingRuntimeCheckpointId ?? normalizedActiveSession.pendingRuntimeCheckpointId,
stopIntentId: runtimeSource.stopIntentId ?? normalizedActiveSession.stopIntentId,
@@ -447,7 +446,6 @@ export function getConversationHeaderSession(
runtimeControlState: runtimeSource.runtimeControlState ?? normalizedActiveSession.runtimeControlState,
canStop: runtimeSource.canStop ?? normalizedActiveSession.canStop,
canResume: runtimeSource.canResume ?? normalizedActiveSession.canResume,
resumeParentTaskId: runtimeSource.resumeParentTaskId ?? normalizedActiveSession.resumeParentTaskId,
resumeParentSessionId: runtimeSource.resumeParentSessionId ?? normalizedActiveSession.resumeParentSessionId,
pendingRuntimeCheckpointId: runtimeSource.pendingRuntimeCheckpointId ?? normalizedActiveSession.pendingRuntimeCheckpointId,
stopIntentId: runtimeSource.stopIntentId ?? normalizedActiveSession.stopIntentId,
@@ -30,6 +30,17 @@ const flushPromises = async () => {
const client = new VisualSocketClient('ws://unit.test', {})
// Company Continue keeps the selected UI channel task separate from the
// durable runtime identity used by the checkpoint handoff.
client.sessionResume('project-a', 'ui-task', 'runtime-session', 'checkpoint-1')
const resumeEnvelope = JSON.parse(
(client as unknown as TestSocketClient).pendingQueue.pop() ?? '{}',
) as Record<string, unknown>
assert.equal(resumeEnvelope.type, 'session_resume')
assert.equal(resumeEnvelope.task_id, 'ui-task')
assert.equal(resumeEnvelope.runtime_session_id, 'runtime-session')
assert.equal(resumeEnvelope.checkpoint_id, 'checkpoint-1')
// A summary and a full request for the same task are distinct correlations.
// Neither Promise may settle merely because the request was queued locally.
const summaryPromise = client.sessionDetail('project-a', 'task-1', { detailLevel: 'summary' })
@@ -41,8 +41,6 @@ interface SocketHandlers {
onProjectSwitched?: (payload: { project_id: string; switch_seq?: string }) => void
onProjectDeleted?: (payload: { project_id: string }) => void
onOrgInfo?: (payload: OrgInfoPayload) => void
onRecoveryStatus?: (payload: any) => void
onRecoveryResult?: (payload: any) => void
onTalentList?: (payload: TalentListPayload) => void
onTalentScanLocal?: (payload: { templates: Array<{ template_id: string; name: string; description: string; category: string; domains: string[]; tags: string[] }> }) => void
onEmployeeDetail?: (payload: EmployeeDetailPayload) => void
@@ -169,7 +167,6 @@ const PROJECT_SCOPED_MESSAGE_TYPES = new Set([
'session_update_title',
'secretary_send',
'project_index',
'recovery_action',
'comms_state',
'comms_read_message',
])
@@ -420,9 +417,22 @@ export class VisualSocketClient {
this.send({ type: 'session_stop', project_id: pid, task_id: taskId })
}
sessionResume(projectId: string, taskId: string, content?: string): void {
sessionResume(
projectId: string,
taskId: string,
runtimeSessionId?: string,
checkpointId?: string,
content?: string,
): void {
const pid = this.requireProjectId(projectId, 'session_resume')
this.send({ type: 'session_resume', project_id: pid, task_id: taskId, content })
this.send({
type: 'session_resume',
project_id: pid,
task_id: taskId,
runtime_session_id: runtimeSessionId,
checkpoint_id: checkpointId,
content,
})
}
sessionComplete(projectId: string, taskId: string): void {
@@ -646,11 +656,6 @@ export class VisualSocketClient {
this.send({ type: 'org_saved_delete', name })
}
recoveryAction(projectId: string, action: 'resume' | 'cancel' | 'scan', parentTaskId?: string): void {
const pid = this.requireProjectId(projectId, 'recovery_action')
this.send({ type: 'recovery_action', project_id: pid, action, parent_task_id: parentTaskId })
}
commsState(projectId: string, opts?: { task_id?: string; session_id?: string }): void {
const pid = this.requireProjectId(projectId, 'comms_state')
this.send({ type: 'comms_state', project_id: pid, ...(opts || {}) })
@@ -801,12 +806,6 @@ export class VisualSocketClient {
if (projectId) this.commsState(projectId)
} catch { /* ignore */ }
break
case 'recovery_status':
this.handlers.onRecoveryStatus?.(parsed.payload)
break
case 'recovery_result':
this.handlers.onRecoveryResult?.(parsed.payload)
break
case 'talent_list':
this.handlers.onTalentList?.(parsed.payload)
break