✨ feat(vscode): 会话交接改走阿里云 OSS 收件箱、换 xz 压缩,另加从 env 文件配置移交存储的 URI
- 会话包传到 OSS 上收件人的收件箱 spx-handoff/<owner>/<repo>/session/<收件人>/<sid>!<发送人>!<会话名>.tar.xz,对象私有;owner/repo/收件人转小写 - 待接收列表就是列自己的收件箱;同一会话再交给同一人先删旧包;接收后删包,包已被别处收走时提示并刷新 - 打包换成 xz 1 级,和工单移交共用 packHandoff / unpackHandoff,删掉 gzip 版 - 删掉邮箱工单整套(mailboxIssue、MAILBOX_*、isMailboxIssue 及看板 / webhook 里的过滤、附件名编解码)和不再用的 Gitea 附件、标签、建工单接口 - session-handoff/accept 与待接收项改用 OSS 对象 key 标识 - 没配移交存储时发送、接收、刷新都提示去配,刷新回空列表 - 进度提示照工单移交:同一条提示依次显示打包、上传、下载多少 MB、解包、装会话 - 新增 URI configure-handoff-store?envFile=<绝对路径>:读 OSS_ENDPOINT / OSS_BUCKET_NAME / OSS_ACCESS_KEY / OSS_SECRET_KEY 保存,和命令共用 saveHandoffStore;URI 入口按 path 统一分发,未知 path 报错
This commit is contained in:
1 parent
81fe14f6dc
commit
fcee4143d4
26 files changed
+542
-515
No files matched your search
@@ -571,7 +571,7 @@ export class KanbanWebviewPanel {
|
||||
return
|
||||
}
|
||||
if (msg.type === 'session-handoff/accept') {
|
||||
void sessionHandoff.handleSessionHandoffAccept(this, msg.attachmentId)
|
||||
void sessionHandoff.handleSessionHandoffAccept(this, msg.key)
|
||||
return
|
||||
}
|
||||
if (msg.type === 'session-handoff/refresh') {
|
||||
|
||||
@@ -99,7 +99,7 @@ export function scratchDir(prefix: string): Promise<string> {
|
||||
}
|
||||
|
||||
/** 读移交存储配置;没配置就 toast 提示去跑「配置移交存储」,返回 undefined。 */
|
||||
async function requireHandoffStore(panel: KanbanWebviewPanel): Promise<HandoffStore | undefined> {
|
||||
export async function requireHandoffStore(panel: KanbanWebviewPanel): Promise<HandoffStore | undefined> {
|
||||
const store = await readHandoffStore(panel.context)
|
||||
if (!store)
|
||||
toast(panel, 'error', HANDOFF_STORE_MISSING_MESSAGE)
|
||||
@@ -110,7 +110,7 @@ async function requireHandoffStore(panel: KanbanWebviewPanel): Promise<HandoffSt
|
||||
* 同一条进度 toast 随步骤改文案,直到流程结束才收掉。文案没变就不重发,
|
||||
* 上传 / 下载的进度回调很密。
|
||||
*/
|
||||
function progressToast(panel: KanbanWebviewPanel, prefix: string): { say: (step: string) => void, dismiss: () => void } {
|
||||
export function progressToast(panel: KanbanWebviewPanel, prefix: string): { say: (step: string) => void, dismiss: () => void } {
|
||||
let id: string | undefined
|
||||
let shown = ''
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
/**
|
||||
* 会话交接:把会话 tab 里的一个 claude 会话交给同事。
|
||||
*
|
||||
* 会话包放在移交存储(阿里云 OSS)上,每个人在每个仓库有一个收件箱目录,
|
||||
* key 的格式见 `src/cc/sessionHandoff.ts`。发送方打包上传进对方的收件箱,
|
||||
* 接收方列自己的收件箱得到待接收列表,接收后把包删掉。
|
||||
*/
|
||||
|
||||
import type { HandoffStore } from '../../oss/handoffStore'
|
||||
import type { KanbanWebviewPanel } from '../KanbanPanel'
|
||||
import type { SessionHandoffPendingItem } from '../messages'
|
||||
import type { RepoCtx } from './handoffFlow'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { packSessionHandoff, unpackSessionHandoff } from '../../cc/handoffArchive'
|
||||
import { formatByteSize, packHandoff, unpackHandoff } from '../../cc/handoffArchive'
|
||||
import { stageClaudeSessionFiles } from '../../cc/handoffBundle'
|
||||
import { HANDOFF_MANIFEST_FILE } from '../../cc/handoffManifest'
|
||||
import { resolveProfilePath } from '../../cc/profiles'
|
||||
@@ -14,32 +25,26 @@ import {
|
||||
} from '../../cc/sessionBundle'
|
||||
import {
|
||||
buildSessionHandoffManifest,
|
||||
parseSessionHandoffAttachmentName,
|
||||
parseSessionHandoffKey,
|
||||
parseSessionHandoffManifest,
|
||||
sessionHandoffAttachmentName,
|
||||
sessionHandoffKey,
|
||||
sessionInboxPrefix,
|
||||
} from '../../cc/sessionHandoff'
|
||||
import { projectsDirFor } from '../../cc/sessionWatcher'
|
||||
import {
|
||||
deleteIssueAttachment,
|
||||
downloadAttachment,
|
||||
listIssueAttachments,
|
||||
listRepoAssignees,
|
||||
uploadIssueAttachment,
|
||||
} from '../../gitea/api'
|
||||
import { ensureMailboxIssue, findMailboxIssue } from '../../gitea/mailboxIssue'
|
||||
import { listRepoAssignees } from '../../gitea/api'
|
||||
import { logger } from '../../logging/logger'
|
||||
import {
|
||||
deleteHandoffBundle,
|
||||
downloadHandoffBundle,
|
||||
isHandoffBundleMissing,
|
||||
listHandoffBundles,
|
||||
uploadHandoffBundle,
|
||||
} from '../../oss/handoffStore'
|
||||
import { readManagedSessions, writeManagedSessions } from '../../sessions/managedStore'
|
||||
import { dismiss, resolveRepoCtx, scratchDir, toast } from './handoffFlow'
|
||||
import { transferProgressText } from './handoff'
|
||||
import { progressToast, requireHandoffStore, resolveRepoCtx, scratchDir, toast } from './handoffFlow'
|
||||
import { pushManagedSessions } from './managedSessions'
|
||||
|
||||
export interface SessionHandoffPendingItem {
|
||||
attachmentId: number
|
||||
sid: string
|
||||
from: string
|
||||
to: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export async function handleSessionHandoffUsers(panel: KanbanWebviewPanel): Promise<void> {
|
||||
const ctx = await resolveRepoCtx(panel)
|
||||
if (!ctx) {
|
||||
@@ -60,27 +65,40 @@ export async function handleSessionHandoffUsers(panel: KanbanWebviewPanel): Prom
|
||||
}
|
||||
}
|
||||
|
||||
/** 列我的收件箱发给 webview。拿不到列表(没配移交存储、没仓库、网络错)也回一个空列表,webview 不用一直等。 */
|
||||
export async function handleSessionHandoffRefresh(panel: KanbanWebviewPanel): Promise<void> {
|
||||
const post = (items: SessionHandoffPendingItem[]): void => panel.postMessage({ type: 'session-handoff/pending', items })
|
||||
const store = await requireHandoffStore(panel)
|
||||
if (!store) {
|
||||
post([])
|
||||
return
|
||||
}
|
||||
const ctx = await resolveRepoCtx(panel)
|
||||
if (!ctx) {
|
||||
panel.postMessage({ type: 'session-handoff/pending', items: [] })
|
||||
post([])
|
||||
return
|
||||
}
|
||||
try {
|
||||
const items = await listPendingForMe(ctx)
|
||||
panel.postMessage({ type: 'session-handoff/pending', items })
|
||||
post(await listPendingForMe(store, ctx))
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({ level: 'warn', source: 'panel', message: '拉取待接收会话失败', details: message })
|
||||
panel.postMessage({ type: 'session-handoff/pending', items: [] })
|
||||
post([])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送方:关掉会话终端 → 收集会话文件、写清单、xz 打包 → 删掉对方收件箱里这个会话的旧包
|
||||
* → 上传 → 本机记下交给了谁。同一条进度提示随步骤改文案,结束才收掉。
|
||||
*/
|
||||
export async function handleSessionHandoffStart(panel: KanbanWebviewPanel, sessionId: string, to: string): Promise<void> {
|
||||
const finish = (): void => panel.postMessage({ type: 'session-handoff/done', sessionId })
|
||||
let spinner: string | undefined
|
||||
let progress: ReturnType<typeof progressToast> | undefined
|
||||
try {
|
||||
const store = await requireHandoffStore(panel)
|
||||
if (!store)
|
||||
return
|
||||
const ctx = await resolveRepoCtx(panel)
|
||||
if (!ctx)
|
||||
return
|
||||
@@ -95,62 +113,47 @@ export async function handleSessionHandoffStart(panel: KanbanWebviewPanel, sessi
|
||||
toast(panel, 'error', `找不到会话文件 ${sessionId},无法交接`)
|
||||
return
|
||||
}
|
||||
const key = sessionHandoffKey({ owner: ctx.owner, repo: ctx.repo, to, sid: session.id, from: ctx.me, name: session.name })
|
||||
|
||||
progress = progressToast(panel, `正在把「${session.name || sessionId.slice(0, 8)}」交给 ${to}`)
|
||||
const say = progress.say
|
||||
|
||||
const existing = panel.managedTerminals.get(sessionId)
|
||||
if (existing) {
|
||||
say('关闭终端')
|
||||
try {
|
||||
existing.dispose()
|
||||
}
|
||||
// 终端已经死了也无所谓,目的只是让 jsonl 不再被写
|
||||
catch {}
|
||||
await new Promise<void>(r => setTimeout(r, 600))
|
||||
}
|
||||
|
||||
spinner = toast(panel, 'info', `正在把「${session.name || sessionId.slice(0, 8)}」交给 ${to}…`, { spinner: true, dismissOnTimer: 120_000 })
|
||||
|
||||
say('收集会话')
|
||||
const manifest = buildSessionHandoffManifest({
|
||||
from: ctx.me,
|
||||
to,
|
||||
workspacePath: ctx.workspaceRoot,
|
||||
session: { id: session.id, name: session.name, profilePath: session.profilePath },
|
||||
})
|
||||
const attachmentName = sessionHandoffAttachmentName({
|
||||
sid: session.id,
|
||||
from: ctx.me,
|
||||
to,
|
||||
name: session.name,
|
||||
})
|
||||
|
||||
// 包必须放在 staging 之外,否则 tar 会把半成品包也打进去。
|
||||
const staging = await scratchDir('spx-session-handoff-')
|
||||
const outDir = await scratchDir('spx-session-handoff-out-')
|
||||
let mailboxIndex: number
|
||||
try {
|
||||
const claudeDir = path.join(staging, 'claude')
|
||||
await stageClaudeSessionFiles(files, claudeDir)
|
||||
await stageClaudeSessionFiles(files, path.join(staging, 'claude'))
|
||||
await fsp.writeFile(path.join(staging, HANDOFF_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`)
|
||||
mailboxIndex = await ensureMailboxIssue(ctx)
|
||||
const archive = path.join(outDir, attachmentName)
|
||||
await packSessionHandoff(staging, archive)
|
||||
for (const old of await listIssueAttachments({ ...ctx, index: mailboxIndex })) {
|
||||
const parsed = parseSessionHandoffAttachmentName(old.name)
|
||||
if (parsed && parsed.sid === session.id && parsed.to === to)
|
||||
await deleteIssueAttachment({ ...ctx, index: mailboxIndex, attachmentId: old.id })
|
||||
const archive = path.join(outDir, 'session-handoff.tar.xz')
|
||||
await packHandoff(staging, archive, { onProgress: bytes => say(`打包 ${formatByteSize(bytes)}`) })
|
||||
// 同一个会话再交给同一人:先删掉对方收件箱里这个会话的旧包,对方只看到最新的一份。
|
||||
for (const old of await listHandoffBundles(store, sessionInboxPrefix({ owner: ctx.owner, repo: ctx.repo, to }))) {
|
||||
if (parseSessionHandoffKey(old.key)?.sid === session.id)
|
||||
await deleteHandoffBundle(store, old.key)
|
||||
}
|
||||
await uploadIssueAttachment({
|
||||
...ctx,
|
||||
index: mailboxIndex,
|
||||
name: attachmentName,
|
||||
data: await fsp.readFile(archive),
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({ level: 'error', source: 'panel', message: `会话交接打包/上传失败 ${sessionId}`, details: message })
|
||||
if (spinner)
|
||||
dismiss(panel, spinner)
|
||||
toast(panel, 'error', `交接失败:${message}`)
|
||||
return
|
||||
await uploadHandoffBundle(store, key, archive, (sent, total) => say(transferProgressText('上传', sent, total)))
|
||||
logger.add({ level: 'info', source: 'panel', message: `会话交接:会话包已上传 (${key}, ${(await fsp.stat(archive)).size} bytes)` })
|
||||
}
|
||||
finally {
|
||||
// rm 失败不该抛出掩盖上面已经判定好的成败——scratch 目录只是垃圾。
|
||||
await fsp.rm(staging, { recursive: true, force: true }).catch(() => {})
|
||||
await fsp.rm(outDir, { recursive: true, force: true }).catch(() => {})
|
||||
}
|
||||
@@ -160,137 +163,139 @@ export async function handleSessionHandoffStart(panel: KanbanWebviewPanel, sessi
|
||||
await writeManagedSessions(ctx.workspaceRoot, data)
|
||||
await pushAll(panel)
|
||||
|
||||
if (spinner)
|
||||
dismiss(panel, spinner)
|
||||
progress.dismiss()
|
||||
toast(panel, 'success', `已交给 ${to}(对方在会话 tab 待接收)`)
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({ level: 'error', source: 'panel', message: `会话交接未预期错误 ${sessionId}`, details: message })
|
||||
logger.add({ level: 'error', source: 'panel', message: `会话交接失败 ${sessionId}`, details: message })
|
||||
progress?.dismiss()
|
||||
toast(panel, 'error', `交接失败:${message}`)
|
||||
if (spinner)
|
||||
dismiss(panel, spinner)
|
||||
}
|
||||
finally {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSessionHandoffAccept(panel: KanbanWebviewPanel, attachmentId: number): Promise<void> {
|
||||
/**
|
||||
* 接收方:只认我在这个仓库收件箱里的 key → 下载 → 解包、校验清单 → 发送方和我的
|
||||
* 工作区路径不同就改写会话里的路径 → 装会话文件 → 记进会话 tab → 删掉 OSS 上的包。
|
||||
* 删包之前失败,包还在列表里,可以重试。
|
||||
*/
|
||||
export async function handleSessionHandoffAccept(panel: KanbanWebviewPanel, key: string): Promise<void> {
|
||||
const finish = (): void => panel.postMessage({ type: 'session-handoff/done', sessionId: '' })
|
||||
let spinner: string | undefined
|
||||
let progress: ReturnType<typeof progressToast> | undefined
|
||||
// extracted 声明在 try 外面:mkdtemp 之前就失败时 finally 不能去 rm 它。
|
||||
let extracted: string | undefined
|
||||
try {
|
||||
const store = await requireHandoffStore(panel)
|
||||
if (!store)
|
||||
return
|
||||
const ctx = await resolveRepoCtx(panel)
|
||||
if (!ctx)
|
||||
return
|
||||
const mailboxIndex = await findMailboxIssue(ctx)
|
||||
if (mailboxIndex === undefined) {
|
||||
toast(panel, 'error', '还没有会话交接邮箱')
|
||||
return
|
||||
}
|
||||
const assets = await listIssueAttachments({ ...ctx, index: mailboxIndex })
|
||||
const asset = assets.find(a => a.id === attachmentId)
|
||||
if (!asset) {
|
||||
toast(panel, 'error', '找不到这份待接收附件')
|
||||
return
|
||||
}
|
||||
const named = parseSessionHandoffAttachmentName(asset.name)
|
||||
if (!named || named.to !== ctx.me) {
|
||||
// key 是 webview 发来的,只认我在当前仓库收件箱里的包。
|
||||
const ref = parseSessionHandoffKey(key)
|
||||
if (!ref || !key.startsWith(sessionInboxPrefix({ owner: ctx.owner, repo: ctx.repo, to: ctx.me }))) {
|
||||
toast(panel, 'error', '这份交接不是给你的')
|
||||
return
|
||||
}
|
||||
const label = ref.name || ref.sid.slice(0, 8)
|
||||
|
||||
spinner = toast(panel, 'info', `正在接收「${named.name || named.sid.slice(0, 8)}」…`, { spinner: true, dismissOnTimer: 120_000 })
|
||||
|
||||
const archiveDir = await scratchDir('spx-session-handoff-in-')
|
||||
const extracted = await scratchDir('spx-session-handoff-ex-')
|
||||
progress = progressToast(panel, `正在接收「${label}」`)
|
||||
const say = progress.say
|
||||
extracted = await scratchDir('spx-session-handoff-in-')
|
||||
// 包名固定,不拿 key 拼本机路径。
|
||||
const archive = path.join(extracted, 'session-handoff.tar.xz')
|
||||
try {
|
||||
const buf = await downloadAttachment({ token: ctx.token, url: asset.browser_download_url })
|
||||
const archive = path.join(archiveDir, asset.name)
|
||||
await fsp.writeFile(archive, buf)
|
||||
await unpackSessionHandoff(archive, extracted)
|
||||
const manifest = parseSessionHandoffManifest(await fsp.readFile(path.join(extracted, HANDOFF_MANIFEST_FILE), 'utf8'))
|
||||
if (manifest.to !== ctx.me)
|
||||
throw new Error(`清单接收人是 ${manifest.to},不是你`)
|
||||
const claudeDir = path.join(extracted, 'claude')
|
||||
if (manifest.workspacePath !== ctx.workspaceRoot) {
|
||||
for (const { id } of manifest.claude) {
|
||||
await rewritePathInFile(path.join(claudeDir, `${id}.jsonl`), manifest.workspacePath, ctx.workspaceRoot)
|
||||
await rewritePathInTree(path.join(claudeDir, id), manifest.workspacePath, ctx.workspaceRoot)
|
||||
}
|
||||
}
|
||||
const dstProjectsDir = projectsDirFor(ctx.workspaceRoot)
|
||||
await fsp.mkdir(dstProjectsDir, { recursive: true })
|
||||
for (const { id } of manifest.claude) {
|
||||
await installClaudeSession({
|
||||
sid: id,
|
||||
srcDir: claudeDir,
|
||||
dstProjectsDir,
|
||||
projectsRoot: claudeProjectsRoot(),
|
||||
})
|
||||
}
|
||||
await downloadHandoffBundle(store, key, archive, (got, total) => say(transferProgressText('下载', got, total)))
|
||||
}
|
||||
catch (err) {
|
||||
if (!isHandoffBundleMissing(err))
|
||||
throw err
|
||||
progress.dismiss()
|
||||
toast(panel, 'error', `「${label}」已经不在收件箱里了,可能在别的机器上接收过`)
|
||||
await handleSessionHandoffRefresh(panel)
|
||||
return
|
||||
}
|
||||
|
||||
const data = await readManagedSessions(ctx.workspaceRoot)
|
||||
const existing = data.sessions.find(s => s.id === manifest.session.id)
|
||||
const profilePath = resolveProfilePath(manifest.session.profilePath)
|
||||
if (existing) {
|
||||
if (manifest.session.name)
|
||||
existing.name = manifest.session.name
|
||||
existing.profilePath = profilePath
|
||||
say('解包')
|
||||
await unpackHandoff(archive, extracted)
|
||||
const manifest = parseSessionHandoffManifest(await fsp.readFile(path.join(extracted, HANDOFF_MANIFEST_FILE), 'utf8'))
|
||||
if (manifest.to !== ctx.me)
|
||||
throw new Error(`清单接收人是 ${manifest.to},不是你`)
|
||||
if (manifest.session.id !== ref.sid)
|
||||
throw new Error(`清单里的会话 ${manifest.session.id} 和收件箱里的 ${ref.sid} 对不上`)
|
||||
|
||||
say('装会话')
|
||||
const claudeDir = path.join(extracted, 'claude')
|
||||
if (manifest.workspacePath !== ctx.workspaceRoot) {
|
||||
for (const { id } of manifest.claude) {
|
||||
await rewritePathInFile(path.join(claudeDir, `${id}.jsonl`), manifest.workspacePath, ctx.workspaceRoot)
|
||||
await rewritePathInTree(path.join(claudeDir, id), manifest.workspacePath, ctx.workspaceRoot)
|
||||
}
|
||||
else {
|
||||
data.sessions.push({
|
||||
id: manifest.session.id,
|
||||
name: manifest.session.name || named.name || manifest.session.id.slice(0, 8),
|
||||
profilePath,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
}
|
||||
await writeManagedSessions(ctx.workspaceRoot, data)
|
||||
await deleteIssueAttachment({ ...ctx, index: mailboxIndex, attachmentId }).catch((err) => {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({ level: 'warn', source: 'panel', message: '接收后删附件失败', details: message })
|
||||
}
|
||||
const dstProjectsDir = projectsDirFor(ctx.workspaceRoot)
|
||||
await fsp.mkdir(dstProjectsDir, { recursive: true })
|
||||
for (const { id } of manifest.claude) {
|
||||
await installClaudeSession({
|
||||
sid: id,
|
||||
srcDir: claudeDir,
|
||||
dstProjectsDir,
|
||||
projectsRoot: claudeProjectsRoot(),
|
||||
})
|
||||
}
|
||||
finally {
|
||||
await fsp.rm(archiveDir, { recursive: true, force: true }).catch(() => {})
|
||||
await fsp.rm(extracted, { recursive: true, force: true }).catch(() => {})
|
||||
|
||||
say('写入会话列表')
|
||||
const data = await readManagedSessions(ctx.workspaceRoot)
|
||||
const existing = data.sessions.find(s => s.id === manifest.session.id)
|
||||
const profilePath = resolveProfilePath(manifest.session.profilePath)
|
||||
if (existing) {
|
||||
if (manifest.session.name)
|
||||
existing.name = manifest.session.name
|
||||
existing.profilePath = profilePath
|
||||
}
|
||||
else {
|
||||
data.sessions.push({
|
||||
id: manifest.session.id,
|
||||
name: manifest.session.name || label,
|
||||
profilePath,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
}
|
||||
await writeManagedSessions(ctx.workspaceRoot, data)
|
||||
try {
|
||||
await deleteHandoffBundle(store, key)
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({ level: 'warn', source: 'panel', message: `接收会话后删除 OSS 上的会话包失败 (${key})`, details: err instanceof Error ? err.message : String(err) })
|
||||
}
|
||||
|
||||
await pushAll(panel)
|
||||
if (spinner)
|
||||
dismiss(panel, spinner)
|
||||
toast(panel, 'success', `已接收「${named.name || named.sid.slice(0, 8)}」,来自 ${named.from}`)
|
||||
progress.dismiss()
|
||||
toast(panel, 'success', `已接收「${label}」,来自 ${manifest.from}`)
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({ level: 'error', source: 'panel', message: `接收会话失败 attachment=${attachmentId}`, details: message })
|
||||
logger.add({ level: 'error', source: 'panel', message: `接收会话失败 ${key}`, details: message })
|
||||
progress?.dismiss()
|
||||
toast(panel, 'error', `接收失败:${message}`)
|
||||
if (spinner)
|
||||
dismiss(panel, spinner)
|
||||
}
|
||||
finally {
|
||||
// finish 先发:下面的 rm 失败不该让 webview 一直等。
|
||||
finish()
|
||||
if (extracted)
|
||||
await fsp.rm(extracted, { recursive: true, force: true }).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
async function listPendingForMe(ctx: { host: string, token: string, owner: string, repo: string, me: string }): Promise<SessionHandoffPendingItem[]> {
|
||||
const mailboxIndex = await findMailboxIssue(ctx)
|
||||
if (mailboxIndex === undefined)
|
||||
return []
|
||||
const assets = await listIssueAttachments({ ...ctx, index: mailboxIndex })
|
||||
async function listPendingForMe(store: HandoffStore, ctx: RepoCtx): Promise<SessionHandoffPendingItem[]> {
|
||||
const objects = await listHandoffBundles(store, sessionInboxPrefix({ owner: ctx.owner, repo: ctx.repo, to: ctx.me }))
|
||||
const items: SessionHandoffPendingItem[] = []
|
||||
for (const a of assets) {
|
||||
const parsed = parseSessionHandoffAttachmentName(a.name)
|
||||
if (!parsed || parsed.to !== ctx.me)
|
||||
continue
|
||||
items.push({
|
||||
attachmentId: a.id,
|
||||
sid: parsed.sid,
|
||||
from: parsed.from,
|
||||
to: parsed.to,
|
||||
name: parsed.name,
|
||||
})
|
||||
for (const o of objects) {
|
||||
const ref = parseSessionHandoffKey(o.key)
|
||||
if (ref)
|
||||
items.push({ key: o.key, sid: ref.sid, from: ref.from, name: ref.name })
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ export interface ManagedSessionShowItem extends ManagedSession {
|
||||
tabOpen?: boolean
|
||||
}
|
||||
|
||||
/** 我收件箱里一份待接收的会话包,`key` 是它在移交存储上的对象 key。 */
|
||||
export interface SessionHandoffPendingItem {
|
||||
attachmentId: number
|
||||
key: string
|
||||
sid: string
|
||||
from: string
|
||||
to: string
|
||||
name: string
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ export type WebviewToExtension
|
||||
| { type: 'handoff/request-reject', issueNumber: number }
|
||||
| { type: 'session-handoff/users' }
|
||||
| { type: 'session-handoff/start', sessionId: string, to: string }
|
||||
| { type: 'session-handoff/accept', attachmentId: number }
|
||||
| { type: 'session-handoff/accept', key: string }
|
||||
| { type: 'session-handoff/refresh' }
|
||||
| { type: 'dependency/set', issueNumber: number, prerequisiteNumber: number }
|
||||
| { type: 'dependency/clear', issueNumber: number, prerequisiteNumber: number }
|
||||
|
||||
Reference in new issue
Block a user