✨ feat(vscode): 工单移交的会话包改走阿里云 OSS、换 xz 压缩,两边进度一步步显示

- 会话包传到 OSS 的 spx-handoff/<owner>/<repo>/issue-<N>-<时间>.tar.xz,上传时对象设成私有;不再挂 Gitea 工单附件
- 新命令「配置移交存储」:endpoint、bucket 存扩展设置,AccessKey 存密钥存储;没配时移交 / 接管直接提示,什么都不改
- 共享 state 字段 handoffAttachmentId 改名 handoffBundleKey(含 spx CLI 与 schema)
- 打包换成 xz 1 级(tar -cJf),去掉 100MB 上限检查和先试压一遍的重复压缩
- 发送方提示打包 / 上传了多少 MB;接管方同一条提示依次显示下载、解包、建 worktree、装会话,完成前不消失
- 会话交接(单会话)仍走 Gitea 附件,Gitea 附件白名单没有 .xz,单独保留 gzip 打包
This commit is contained in:
cruldra committed 2026-10-08 07:17:26 +08:00
1 parent a33d3d2251
commit 499cfad7d7
39 files changed
+1039 -526

No files matched your search

+1 -1
View File
@@ -539,7 +539,7 @@ export function App() {
<HandoffModal
open={handoffIssue !== null}
title={handoffIssue !== null ? `移交 #${handoffIssue}` : '移交'}
description="会把本机的 worktree 删除、会话记录打包挂到工单附件,并把工单指派给对方。移交前分支必须已全部 push。"
description="会把本机的 worktree 删除、会话记录打包传到移交存储(阿里云 OSS),并把工单指派给对方。移交前分支必须已全部 push。"
users={handoffUsers && handoffUsers.issueNumber === handoffIssue ? handoffUsers.users : null}
onCancel={() => setHandoffIssue(null)}
onSubmit={(to) => {
@@ -129,7 +129,7 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
: null}
{readOnly && ownerLogin ? <OwnerBadge login={ownerLogin} avatar={ownerAvatar} /> : null}
</div>
{issue.handoffAttachmentId
{issue.handoffBundleKey
? (
<span
className="absolute bottom-1 right-1.5 rounded border border-blue-400/50 bg-blue-400/10 px-1 text-[9px] text-blue-400"
@@ -918,7 +918,7 @@ export function IssueDetailPanel({
avoid acting on a gitea issue of the same number. */}
{issue.source !== 'youtrack' && !readOnly && (
<>
{issue.handoffAttachmentId && me !== undefined && (issue.assignees ?? []).includes(me) && (
{issue.handoffBundleKey && me !== undefined && (issue.assignees ?? []).includes(me) && (
<button
type="button"
data-tooltip={`接管 ${issue.handoffFrom ?? '?'} 的移交`}
@@ -930,7 +930,7 @@ export function IssueDetailPanel({
{isHandoffRunning(issue.number) ? <Loader2 className="size-3.5 animate-spin" /> : <Download className="size-3.5" />}
</button>
)}
{!issue.handoffAttachmentId && issue.column !== 'done' && ((issue.assignees ?? []).length === 0 || (me !== undefined && (issue.assignees ?? []).includes(me))) && (
{!issue.handoffBundleKey && issue.column !== 'done' && ((issue.assignees ?? []).length === 0 || (me !== undefined && (issue.assignees ?? []).includes(me))) && (
<button
type="button"
data-tooltip="移交给同事"
@@ -942,7 +942,7 @@ export function IssueDetailPanel({
{isHandoffRunning(issue.number) ? <Loader2 className="size-3.5 animate-spin" /> : <ArrowRightLeft className="size-3.5" />}
</button>
)}
{issue.handoffAttachmentId && !(me !== undefined && (issue.assignees ?? []).includes(me)) && (
{issue.handoffBundleKey && !(me !== undefined && (issue.assignees ?? []).includes(me)) && (
<span
className="inline-flex shrink-0 items-center justify-center rounded border border-[var(--vscode-panel-border)] p-1 text-xs opacity-70"
data-tooltip={`${issue.handoffFrom ?? '?'} 已移交,等 ${(issue.assignees ?? []).join(', ') || '?'} 接管`}
+2 -2
View File
@@ -320,9 +320,9 @@ export interface UseIssuesResult {
handoffUsers: { issueNumber: number, users: string[] } | null
/** 拉取某工单可移交的候选用户列表,结果通过 `handoffUsers` 返回。 */
requestHandoffUsers: (issueNumber: number) => void
/** 发起移交:删本机 worktree、打包会话记录挂到附件、把工单指派给对方。 */
/** 发起移交:删本机 worktree、打包会话记录传到 OSS、把工单指派给对方。 */
startHandoff: (issueNumber: number, to: string) => void
/** 接管一个待接管工单:拉取移交附件、恢复会话与 worktree。 */
/** 接管一个待接管工单:从 OSS 下载会话包、恢复会话与 worktree。 */
acceptHandoff: (issueNumber: number) => void
/** 审查列里向同事申请把工单移交给自己。 */
askHandoff: (issueNumber: number) => void
+1 -1
View File
@@ -93,7 +93,7 @@ export type ExtensionToWebview
| { type: 'issues/loaded' }
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean, me?: string, avatars?: Record<string, string> }
| { type: 'issues/error', message: string }
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, reviewPassed?: boolean, specFile?: string, planFile?: string, prototypeFile?: string, prDiffFile?: string | null, sessionId?: string | null, implementSessionId?: string | null, reviewSessionId?: string | null, missingSessionIds?: string[], testSessionId?: string | null, prototypeSessionId?: string | null, pr?: string | null, implementStatus?: 'running' | 'done' | 'failed' | null, column?: IssueColumn, worktreePath?: string | null, prMerged?: boolean, prMergedAt?: string | null, branch?: string | null, color?: string, worktreeExists?: boolean, brainstormTabOpen?: boolean, implementTabOpen?: boolean, reviewTabOpen?: boolean, testTabOpen?: boolean, prototypeTabOpen?: boolean, profilePath?: string | null, brainstormProfilePath?: string | null, testProfilePath?: string | null, prototypeProfilePath?: string | null, assignees?: string[], handoffAttachmentId?: string | null, handoffFrom?: string | null, handoffRequestFrom?: string | null, handoffRequestError?: string | null } }
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, reviewPassed?: boolean, specFile?: string, planFile?: string, prototypeFile?: string, prDiffFile?: string | null, sessionId?: string | null, implementSessionId?: string | null, reviewSessionId?: string | null, missingSessionIds?: string[], testSessionId?: string | null, prototypeSessionId?: string | null, pr?: string | null, implementStatus?: 'running' | 'done' | 'failed' | null, column?: IssueColumn, worktreePath?: string | null, prMerged?: boolean, prMergedAt?: string | null, branch?: string | null, color?: string, worktreeExists?: boolean, brainstormTabOpen?: boolean, implementTabOpen?: boolean, reviewTabOpen?: boolean, testTabOpen?: boolean, prototypeTabOpen?: boolean, profilePath?: string | null, brainstormProfilePath?: string | null, testProfilePath?: string | null, prototypeProfilePath?: string | null, assignees?: string[], handoffBundleKey?: string | null, handoffFrom?: string | null, handoffRequestFrom?: string | null, handoffRequestError?: string | null } }
/** 某个会话 id 的开 tab 请求已处理完(开成、复用或被拒都算),webview 据此停掉行内 spinner。 */
| { type: 'session/tab-settled', sessionId: string }
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
+2 -2
View File
@@ -101,8 +101,8 @@ export interface Issue {
assignees?: string[]
/** Gitea 工单创建者 login;无 assignee 的工单靠它判定归属与卡片头像。 */
poster?: string
/** 移交会话包的 Gitea 附件 id(字符串);存在即「待接管」。 */
handoffAttachmentId?: string
/** 移交会话包在 OSS 上的对象 key;存在即「待接管」。 */
handoffBundleKey?: string
/** 发起移交的 Gitea login。 */
handoffFrom?: string
/** 谁在申请把这个审查工单移交给自己。 */