✨ feat(vscode): 新增第五种工单会话「原型会话」
照测试会话那条链路补一份 prototype:
- 消息与路由:session/start-prototype、session/resume-prototype、
issue/update-prototype-profile-path,都进只读门禁
- 会话:handleStartPrototypeSession / handleResumePrototypeSession,
终端名 issue-N-原型;cwd 有 worktree 用 worktree、没有退主仓库根
(原 resolveTestSessionCwd 改名 resolveWorktreeOrRootCwd 两种会话共用)
- 不要求有 PR,任何阶段都能起;前置工单锁仍然拦
- 提示词:新增 prompts/prototype.md,设置里可改,占位符 {issueNumber}
- profile:工单级 prototypeProfilePath > 设置里的原型默认 profile >
内置默认,与其它会话互不回退
- 状态字段 prototypeSessionId / prototypeProfilePath / prototypeTabOpen
贯通 gitea、youtrack、本机 state 与移交包
- 详情面板加「原型会话id」「原型配置文件」两行,设置面板加对应两项
Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
1 parent
3fff796a61
commit
b62e58d739
27 files changed
+760
-50
No files matched your search
@@ -117,6 +117,7 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber:
|
||||
let fromColumn: IssueColumn | undefined
|
||||
let implementSessionId: string | undefined
|
||||
let testSessionId: string | undefined
|
||||
let prototypeSessionId: string | undefined
|
||||
let featureBranchFromState: string | undefined
|
||||
try {
|
||||
const stateObj = await panel.readIssueState(issueNumber)
|
||||
@@ -134,6 +135,8 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber:
|
||||
implementSessionId = stateObj.implementSessionId
|
||||
if (typeof stateObj.testSessionId === 'string' && stateObj.testSessionId.length > 0)
|
||||
testSessionId = stateObj.testSessionId
|
||||
if (typeof stateObj.prototypeSessionId === 'string' && stateObj.prototypeSessionId.length > 0)
|
||||
prototypeSessionId = stateObj.prototypeSessionId
|
||||
if (typeof stateObj.branch === 'string' && stateObj.branch.length > 0)
|
||||
featureBranchFromState = stateObj.branch
|
||||
}
|
||||
@@ -394,13 +397,13 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber:
|
||||
issueNumber,
|
||||
})
|
||||
|
||||
// 删 worktree 前把实施/测试会话整包(jsonl + 子目录)搬到主仓库 projects 目录,
|
||||
// 删 worktree 前把实施/测试/原型会话整包(jsonl + 子目录)搬到主仓库 projects 目录,
|
||||
// 并清掉 worktree 侧副本:新版 claude 遇到重复 sid 会拒绝 resume。
|
||||
if ((implementSessionId || testSessionId) && worktreePath) {
|
||||
if ((implementSessionId || testSessionId || prototypeSessionId) && worktreePath) {
|
||||
const worktreeAbs = resolveWorktreePath(worktreePath, workspaceRoot)
|
||||
const srcProjectsDir = projectsDirFor(worktreeAbs)
|
||||
const dstProjectsDir = projectsDirFor(workspaceRoot)
|
||||
for (const sid of [implementSessionId, testSessionId]) {
|
||||
for (const sid of [implementSessionId, testSessionId, prototypeSessionId]) {
|
||||
if (typeof sid !== 'string' || sid.length === 0)
|
||||
continue
|
||||
try {
|
||||
@@ -2210,6 +2213,95 @@ export async function handleUpdateTestProfilePath(panel: KanbanWebviewPanel, iss
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a per-issue `prototypeProfilePath` override into the issue's state JSON
|
||||
* comment. 原型 start/resume 用它(见 resolvePrototypeProfilePath);
|
||||
* 空 = 设置里的「原型默认配置文件」,再空 = getDefaultProfilePath(),与其它会话互不回退。
|
||||
*/
|
||||
export async function handleUpdatePrototypeProfilePath(panel: KanbanWebviewPanel, issueNumber: number, prototypeProfilePath: string): Promise<void> {
|
||||
const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
if (!workspaceRoot) {
|
||||
panel.postMessage({
|
||||
type: 'toast/show',
|
||||
id: makeNonce(),
|
||||
level: 'error',
|
||||
message: '请先打开一个工作区文件夹',
|
||||
dismissOnTimer: 5000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const remote = await detectRepo(workspaceRoot)
|
||||
if (!remote) {
|
||||
panel.postMessage({
|
||||
type: 'toast/show',
|
||||
id: makeNonce(),
|
||||
level: 'error',
|
||||
message: '当前工作区没有 Gitea 远程仓库',
|
||||
dismissOnTimer: 5000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const token = await getToken(panel.context, remote.host)
|
||||
if (!token) {
|
||||
panel.postMessage({
|
||||
type: 'toast/show',
|
||||
id: makeNonce(),
|
||||
level: 'error',
|
||||
message: '请先完成 Gitea 配置',
|
||||
dismissOnTimer: 5000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Snapshot the previous value so we can roll the webview back on failure
|
||||
// without re-broadcasting the whole issues list.
|
||||
let previousValue: string | undefined
|
||||
try {
|
||||
const existingState = await panel.readIssueState(issueNumber)
|
||||
previousValue = typeof existingState.prototypeProfilePath === 'string'
|
||||
? existingState.prototypeProfilePath
|
||||
: undefined
|
||||
}
|
||||
catch {
|
||||
// If we can't read the previous state, leave previousValue undefined —
|
||||
// the rollback patch will clear the override, falling back to the default.
|
||||
previousValue = undefined
|
||||
}
|
||||
|
||||
try {
|
||||
await panel.mergeIssueState(issueNumber, { prototypeProfilePath })
|
||||
logger.add({
|
||||
level: 'info',
|
||||
source: 'panel',
|
||||
message: `工单 #${issueNumber} prototypeProfilePath=${prototypeProfilePath} 已持久化`,
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({
|
||||
level: 'error',
|
||||
source: 'panel',
|
||||
message: `持久化 prototypeProfilePath 失败 (issue #${issueNumber})`,
|
||||
details: message,
|
||||
})
|
||||
panel.postMessage({
|
||||
type: 'toast/show',
|
||||
id: makeNonce(),
|
||||
level: 'error',
|
||||
message: `保存工单 #${issueNumber} 原型配置文件失败: ${message}`,
|
||||
dismissOnTimer: 6000,
|
||||
})
|
||||
// Roll back the optimistic update in the webview.
|
||||
panel.postMessage({
|
||||
type: 'issue/patch',
|
||||
issueNumber,
|
||||
patch: { prototypeProfilePath: previousValue },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleOpenFile(panel: KanbanWebviewPanel, relPath: string): Promise<void> {
|
||||
const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
if (!workspaceRoot) {
|
||||
|
||||
Reference in new issue
Block a user