/** * 会话交接:mailbox 工单识别、附件文件名、managed-session 清单。 * * 与工单移交的 handoff.json 分开:那边有 issue/worktree/四种 sid,这边只有一个 * claude 会话。混用解析会把会话包当工单包读崩。 */ export const MAILBOX_TITLE = 'spx-session-mailbox' export const MAILBOX_LABEL = 'spx-session-mailbox' export const MAILBOX_MARKER = '' export const MAILBOX_BODY = `${MAILBOX_MARKER} Superwork 会话交接邮箱。不要当任务处理,不要关闭。 ` const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i const LOGIN_RE = /^[\w.-]+$/ const FILENAME_RE = /^spx-handoff-session!([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})!([\w.-]+)!([\w.-]+)!([^!]*)\.tgz$/i export interface SessionHandoffManifest { version: 1 kind: 'managed-session' from: string to: string createdAt: string workspacePath: string session: { id: string, name: string, profilePath?: string } claude: Array<{ id: string }> } export interface SessionHandoffAttachmentRef { sid: string from: string to: string name: string } export function isMailboxIssue(issue: { title?: string body?: string labels?: Array<{ name: string }> | null }): boolean { if (issue.title === MAILBOX_TITLE) return true if ((issue.labels ?? []).some(l => l.name === MAILBOX_LABEL)) return true if (typeof issue.body === 'string' && issue.body.includes(MAILBOX_MARKER)) return true return false } export function slugSessionName(name: string): string { const trimmed = name.trim() if (!trimmed) return '' return trimmed.replace(/[^\p{L}\p{N}._-]+/gu, '_').slice(0, 40) } export function sessionHandoffAttachmentName(opts: { sid: string from: string to: string name: string }): string { if (!UUID_RE.test(opts.sid)) throw new Error(`会话 id 不是 UUID:${opts.sid}`) if (!LOGIN_RE.test(opts.from)) throw new Error(`from 含非法字符:${opts.from}`) if (!LOGIN_RE.test(opts.to)) throw new Error(`to 含非法字符:${opts.to}`) return `spx-handoff-session!${opts.sid}!${opts.from}!${opts.to}!${slugSessionName(opts.name)}.tgz` } export function parseSessionHandoffAttachmentName(filename: string): SessionHandoffAttachmentRef | undefined { const m = FILENAME_RE.exec(filename) if (!m) return undefined return { sid: m[1], from: m[2], to: m[3], name: m[4] ?? '' } } function optString(v: unknown): string | undefined { return typeof v === 'string' && v.length > 0 ? v : undefined } export function parseSessionHandoffManifest(json: string): SessionHandoffManifest { const raw = JSON.parse(json) as Record if (!raw || typeof raw !== 'object') throw new Error('handoff.json 不是对象') if (raw.version !== 1) throw new Error(`handoff.json version 不支持:${String(raw.version)}`) if (raw.kind !== 'managed-session') throw new Error(`handoff.json kind 不是 managed-session:${String(raw.kind)}`) const from = optString(raw.from) const to = optString(raw.to) const createdAt = optString(raw.createdAt) const workspacePath = optString(raw.workspacePath) if (!from || !to || !createdAt || !workspacePath) throw new Error('handoff.json 缺少 from / to / createdAt / workspacePath') if (!LOGIN_RE.test(from) || !LOGIN_RE.test(to)) throw new Error('handoff.json from/to 非法') const sessionRaw = (raw.session ?? {}) as Record const id = optString(sessionRaw.id) const name = typeof sessionRaw.name === 'string' ? sessionRaw.name : '' if (!id || !UUID_RE.test(id)) throw new Error(`handoff.json session.id 非法:${String(sessionRaw.id)}`) const claudeRaw = Array.isArray(raw.claude) ? raw.claude : [] const claude = claudeRaw.map((e) => { const o = e as Record const cid = optString(o.id) if (!cid || !UUID_RE.test(cid)) throw new Error(`handoff.json claude 条目非法:${JSON.stringify(e)}`) return { id: cid } }) if (claude.length === 0) throw new Error('handoff.json 没有 claude 会话文件') const profilePath = optString(sessionRaw.profilePath) return { version: 1, kind: 'managed-session', from, to, createdAt, workspacePath, session: { id, name, ...(profilePath ? { profilePath } : {}) }, claude, } } export function buildSessionHandoffManifest(opts: { from: string to: string workspacePath: string session: { id: string, name: string, profilePath?: string } createdAt?: string }): SessionHandoffManifest { if (!UUID_RE.test(opts.session.id)) throw new Error(`会话 id 不是 UUID:${opts.session.id}`) if (!LOGIN_RE.test(opts.from) || !LOGIN_RE.test(opts.to)) throw new Error('from/to 非法') return { version: 1, kind: 'managed-session', from: opts.from, to: opts.to, createdAt: opts.createdAt ?? new Date().toISOString(), workspacePath: opts.workspacePath, session: { id: opts.session.id, name: opts.session.name, ...(opts.session.profilePath ? { profilePath: opts.session.profilePath } : {}), }, claude: [{ id: opts.session.id }], } }