feat(codex): import and resume sessions from runners (#1088)

* fix codex import resume flow

* fix hub restart session active state

* fix codex transcript workspace scoping

* Address Codex import review findings

* Fix Codex import machine selection

* Update Codex sessions error test

* Address Codex import review findings

* Preserve forked Codex session id on sync

* Make Codex duplicate cleanup source-aware

* Handle Codex archive failures

* Limit existing session flag to Codex

* Preserve Codex import machine binding

* fix: rebase runner Codex import onto current main

* fix: preserve runner-scoped Codex import behavior

---------

Co-authored-by: syy <815728149@qq.com>
This commit is contained in:
NightWatcher314
2026-07-19 14:14:42 +08:00
committed by GitHub
co-authored by syy
parent 651c83a1d9
commit 64834467e3
42 changed files with 2536 additions and 133 deletions
+52
View File
@@ -126,6 +126,58 @@ export const CursorChatStoreStatusSchema = z.object({
export type CursorChatStoreStatus = z.infer<typeof CursorChatStoreStatusSchema>
export const CodexImportedMessageSchema = z.union([
z.object({
role: z.literal('user'),
content: z.object({ type: z.literal('text'), text: z.string() }),
meta: z.object({ sentFrom: z.literal('cli') })
}),
z.object({
role: z.literal('agent'),
content: z.object({ type: z.literal('codex'), data: z.unknown() }),
meta: z.object({ sentFrom: z.literal('cli') })
})
])
export const CodexLocalSessionSummarySchema = z.object({
id: z.string().min(1),
title: z.string(),
lastUserMessage: z.string().nullable().optional(),
cwd: z.string().nullable().optional(),
file: z.string().min(1),
modifiedAt: z.number(),
originator: z.string().nullable().optional(),
cliVersion: z.string().nullable().optional(),
source: z.string().nullable().optional(),
threadSource: z.string().nullable().optional(),
forkedFromId: z.string().nullable().optional()
})
export const CodexLocalSessionWithMessagesSchema = CodexLocalSessionSummarySchema.extend({
messages: z.array(CodexImportedMessageSchema)
})
export const ListCodexSessionsRpcRequestSchema = z.object({
cwd: z.string().nullable().optional(),
sessionIds: z.array(z.string().min(1)).optional()
})
export const ListCodexSessionsRpcResponseSchema = z.union([
z.object({ success: z.literal(true), sessions: z.array(z.union([CodexLocalSessionSummarySchema, CodexLocalSessionWithMessagesSchema])) }),
z.object({ success: z.literal(false), error: z.string() })
])
export const ArchiveCodexSessionRpcRequestSchema = z.object({ sessionId: z.string().min(1) })
export const ArchiveCodexSessionRpcResponseSchema = z.union([
z.object({ success: z.literal(true), archivedPath: z.string() }),
z.object({ success: z.literal(false), error: z.string() })
])
export type ListCodexSessionsRpcRequest = z.infer<typeof ListCodexSessionsRpcRequestSchema>
export type ListCodexSessionsRpcResponse = z.infer<typeof ListCodexSessionsRpcResponseSchema>
export type ArchiveCodexSessionRpcRequest = z.infer<typeof ArchiveCodexSessionRpcRequestSchema>
export type ArchiveCodexSessionRpcResponse = z.infer<typeof ArchiveCodexSessionRpcResponseSchema>
export const SessionCollaborationModeRequestSchema = z.object({
mode: CodexCollaborationModeSchema
})
+2
View File
@@ -27,6 +27,8 @@ export const RPC_METHODS = {
ListSlashCommands: 'listSlashCommands',
ListSkills: 'listSkills',
ListCodexModels: 'listCodexModels',
ListCodexSessions: 'listCodexSessions',
ArchiveCodexSession: 'archiveCodexSession',
ListCursorModels: 'listCursorModels',
ListPiModels: 'listPiModels',
ListOpencodeModels: 'listOpencodeModels',
+3
View File
@@ -35,6 +35,9 @@ export const MetadataSchema = z.object({
machineId: z.string().optional(),
claudeSessionId: z.string().optional(),
codexSessionId: z.string().optional(),
// 原始 Codex thread id。导入 Codex 历史后,HAPI 会 fork 出自己的续写 thread
// codexSessionId 保存 fork 后的 threadcodexSourceSessionId 保留来源 thread 便于同步/展示。
codexSourceSessionId: z.string().optional(),
geminiSessionId: z.string().optional(),
opencodeSessionId: z.string().optional(),
grokSessionId: z.string().optional(),
+2
View File
@@ -59,6 +59,7 @@ export type SessionSummary = {
/** Epoch ms of the soonest uninvoked future scheduled message, or null. */
nextScheduledAt: number | null
model: string | null
modelReasoningEffort?: string | null
effort: string | null
}
@@ -146,6 +147,7 @@ export function toSessionSummary(session: Session): SessionSummary {
futureScheduledMessageCount: 0,
nextScheduledAt: null,
model: session.model,
modelReasoningEffort: session.modelReasoningEffort,
effort: session.effort
}
}