✨ 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:
cruldra committed 2026-10-08 07:17:26 +08:00
1 parent 81fe14f6dc
commit fcee4143d4
26 files changed
+542 -515

No files matched your search

@@ -0,0 +1,47 @@
import type { ExtensionContext, Uri } from 'vscode'
import { promises as fsp } from 'node:fs'
import * as path from 'node:path'
import { window } from 'vscode'
import { logger } from '../logging/logger'
import { parseHandoffStoreEnv } from '../oss/handoffStoreEnv'
import { saveHandoffStore } from '../oss/handoffStoreSetup'
/**
* 从 env 文件配置移交存储,由 `uriHandler.ts` 分发过来:
*
* vscode://clurdra.superpowers-vscode-clurdra/configure-handoff-store?envFile=<env 文件的绝对路径>
*
* 读文件里 `OSS_ENDPOINT` / `OSS_BUCKET_NAME` / `OSS_ACCESS_KEY` / `OSS_SECRET_KEY` 四个变量,
* 齐了就和「配置移交存储」命令一样保存;参数不对、文件读不到、缺项都只报错,什么都不改。
* 提示和日志里只出现 bucket、endpoint 和变量名,AccessKey 不出现。
*/
export async function handleConfigureHandoffStoreUri(context: ExtensionContext, uri: Uri): Promise<void> {
const envFile = (new URLSearchParams(uri.query).get('envFile') ?? '').trim()
if (!envFile) {
void window.showErrorMessage('configure-handoff-store URI 缺少 envFile 参数')
return
}
if (!path.isAbsolute(envFile)) {
void window.showErrorMessage(`configure-handoff-store URI:envFile 要写绝对路径(${envFile})`)
return
}
let text: string
try {
text = await fsp.readFile(envFile, 'utf8')
}
catch (err) {
void window.showErrorMessage(`configure-handoff-store URI:读不到 ${envFile}:${err instanceof Error ? err.message : String(err)}`)
return
}
const parsed = parseHandoffStoreEnv(text)
if (!parsed.ok) {
void window.showErrorMessage(`configure-handoff-store URI:${envFile} 里缺少 ${parsed.missing.join('、')}`)
return
}
await saveHandoffStore(context, parsed.store)
logger.add({
level: 'info',
source: 'uri',
message: `URI 已保存移交存储:${parsed.store.bucket}(${parsed.store.endpoint}),来自 ${envFile}`,
})
}
+3 -22
View File
@@ -1,4 +1,4 @@
import type { ExtensionContext, Uri } from 'vscode'
import type { Uri } from 'vscode'
import { promises as fsp } from 'node:fs'
import { window, workspace } from 'vscode'
import { listClaudeProfiles } from '../cc/profiles'
@@ -6,34 +6,15 @@ import { logger } from '../logging/logger'
import { createCcSessionTab } from '../panel/handlers/managedSessions'
/**
* 外部触发开 cc 会话 tab 的 URI 入口(供 delegate-fix skill 等外部方调用):
* 外部触发开 cc 会话 tab(供 delegate-fix skill 等外部方调用),由 `uriHandler.ts` 分发过来:
*
* vscode://clurdra.superpowers-vscode-clurdra/create-session
* ?profile=<profile 名,不带 .json>
* &name=<终端 tab 名,如 fix:fable5>
* &prompt=<初始 prompt>
* &cwd=<绝对路径,可选,缺省用 workspaceRoot>
*
* 编码契约:VS Code 把外部 URI 交给扩展前会对 query 整体做一次 percent-decode
* (vs/base/common/uri.ts 的 URI.parse),这里的 URLSearchParams 是第二次 decode。
* 因此发送方对参数值必须做两层 urlencode,值里的 & = % + 等字符才能安全通过。
*/
export function registerCreateSessionUriHandler(context: ExtensionContext): void {
context.subscriptions.push(window.registerUriHandler({
handleUri: (uri: Uri): void => {
void handleCreateSessionUri(uri).catch((err) => {
void window.showErrorMessage(`处理 create-session URI 失败:${err instanceof Error ? err.message : String(err)}`)
})
},
}))
}
async function handleCreateSessionUri(uri: Uri): Promise<void> {
if (uri.path !== '/create-session') {
void window.showErrorMessage(`未知的 superpowers URI path: ${uri.path}`)
return
}
export async function handleCreateSessionUri(uri: Uri): Promise<void> {
const params = new URLSearchParams(uri.query)
const profileName = (params.get('profile') ?? '').trim()
const name = (params.get('name') ?? '').trim()
+33
View File
@@ -0,0 +1,33 @@
import type { ExtensionContext, Uri } from 'vscode'
import { window } from 'vscode'
import { handleConfigureHandoffStoreUri } from './configureHandoffStoreUri'
import { handleCreateSessionUri } from './createSessionUriHandler'
/** 每个 path 一个路由,参数说明见各自的文件。 */
const ROUTES = new Map<string, (context: ExtensionContext, uri: Uri) => Promise<void>>([
['/create-session', (_context, uri) => handleCreateSessionUri(uri)],
['/configure-handoff-store', handleConfigureHandoffStoreUri],
])
/**
* 扩展的外部 URI 入口 `vscode://clurdra.superpowers-vscode-clurdra/<path>?<参数>`:按 path
* 分给 [ROUTES] 里的路由,未知 path 报错。
*
* 编码契约:VS Code 把外部 URI 交给扩展前会对 query 整体做一次 percent-decode
* (vs/base/common/uri.ts 的 URI.parse),路由里的 URLSearchParams 是第二次 decode。
* 因此发送方对参数值必须做两层 urlencode,值里的 & = % + 空格等字符才能安全通过。
*/
export function registerUriHandler(context: ExtensionContext): void {
context.subscriptions.push(window.registerUriHandler({
handleUri: (uri: Uri): void => {
const route = ROUTES.get(uri.path)
if (!route) {
void window.showErrorMessage(`未知的 superpowers URI path: ${uri.path}`)
return
}
void route(context, uri).catch((err) => {
void window.showErrorMessage(`处理 ${uri.path.slice(1)} URI 失败:${err instanceof Error ? err.message : String(err)}`)
})
},
}))
}