✨ feat(vscode): 实施和审查会话行加启动箭头
不用再把卡片拖到「实施中」「审查」列: - 实施会话id 行在有计划文件且没实施会话时显示 ▶,等同拖进实施中列 - 审查会话id 行在有 PR 时显示 ▶,按 PR 事件那套流程起 codex 审查 Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
1 parent
620b7c73c8
commit
410a04da77
9 files changed
+101
-8
No files matched your search
@@ -439,6 +439,10 @@ export class KanbanWebviewPanel {
|
||||
void sessions.handleResumeReviewSession(this, msg.sessionId, msg.issueNumber, msg.cwd)
|
||||
return
|
||||
}
|
||||
if (msg.type === 'session/start-review') {
|
||||
void sessions.handleStartReviewSession(this, msg.issueNumber)
|
||||
return
|
||||
}
|
||||
if (msg.type === 'session/start-test') {
|
||||
void sessions.handleStartTestSession(this, msg.issueNumber)
|
||||
return
|
||||
|
||||
@@ -13,7 +13,7 @@ import { buildCcCommand } from '../../cc/ccCommand'
|
||||
import { buildCodexCommandString } from '../../cc/codexCommand'
|
||||
import { watchForNewCodexSession } from '../../cc/codexSessionWatcher'
|
||||
import { resolveProfilePath } from '../../cc/profiles'
|
||||
import { getBrainstormContinuePrompt, getImplementPlanPrompt } from '../../cc/prompts'
|
||||
import { getBrainstormContinuePrompt, getImplementPlanPrompt, getReviewPrompt } from '../../cc/prompts'
|
||||
import { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher'
|
||||
import { detectRepo } from '../../git/remote'
|
||||
import { ensureWorktree, resolveWorktreePath } from '../../git/worktree'
|
||||
@@ -1270,6 +1270,57 @@ export async function handleStartTestSession(panel: KanbanWebviewPanel, issueNum
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动启动审查会话:等同 PR 事件触发的自动审查,只是入口在详情面板的
|
||||
* 「审查会话id」行。必须已有 PR——审查提示词就是让 codex 去看这个 PR。
|
||||
*/
|
||||
export async function handleStartReviewSession(panel: KanbanWebviewPanel, issueNumber: number): Promise<void> {
|
||||
const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
if (!workspaceRoot) {
|
||||
void window.showErrorMessage('请先打开一个工作区文件夹')
|
||||
return
|
||||
}
|
||||
|
||||
const lockCheck = await panel.resolveLockedReason(issueNumber)
|
||||
if (lockCheck.locked) {
|
||||
await window.showWarningMessage(`等待前置工单 #${lockCheck.prerequisiteNumber} 完成`)
|
||||
return
|
||||
}
|
||||
|
||||
let pr: string | undefined
|
||||
let worktreePath = ''
|
||||
try {
|
||||
const stateObj = await panel.readIssueState(issueNumber)
|
||||
if (typeof stateObj.pr === 'string' && stateObj.pr.length > 0)
|
||||
pr = stateObj.pr.replace(/\(已合并\)\s*$/, '')
|
||||
if (typeof stateObj.worktreePath === 'string')
|
||||
worktreePath = stateObj.worktreePath
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'review',
|
||||
message: `读取 #${issueNumber} state JSON 失败,无法启动审查会话`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
|
||||
if (!pr) {
|
||||
void window.showWarningMessage('该工单还没有 PR,无法启动审查会话')
|
||||
return
|
||||
}
|
||||
|
||||
const ok = await triggerAutoReviewTab(panel, {
|
||||
issueNumber,
|
||||
prNumber: pr,
|
||||
prompt: getReviewPrompt(panel.context, { prNumber: pr }),
|
||||
worktreePath,
|
||||
workspaceRoot,
|
||||
})
|
||||
if (!ok)
|
||||
void window.showErrorMessage(`启动审查会话失败 #${issueNumber},详见扩展日志`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复一个已存在的测试 cc 会话:详情面板点测试会话行(testTabOpen=false 时)
|
||||
* 触发,照搬 handleResumeSession 的 implement 分支,但 kind='test'、终端名
|
||||
|
||||
@@ -193,6 +193,7 @@ export type WebviewToExtension
|
||||
| { type: 'session/focus', issueNumber: number }
|
||||
| { type: 'session/resume-review', sessionId: string, issueNumber: number, cwd?: string }
|
||||
| { type: 'session/resume-test', sessionId: string, issueNumber: number, cwd?: string }
|
||||
| { type: 'session/start-review', issueNumber: number }
|
||||
| { type: 'session/start-test', issueNumber: number }
|
||||
| { type: 'editor/open-file', path: string }
|
||||
| { type: 'profiles/list' }
|
||||
|
||||
@@ -14,6 +14,7 @@ export const OWNER_ONLY_MESSAGE_TYPES: ReadonlySet<string> = new Set([
|
||||
'session/focus',
|
||||
'session/resume-review',
|
||||
'session/resume-test',
|
||||
'session/start-review',
|
||||
'session/start-test',
|
||||
'session/close-tab',
|
||||
'brainstorm/continue',
|
||||
|
||||
@@ -41,6 +41,7 @@ export function App() {
|
||||
openUrl,
|
||||
resumeSession,
|
||||
isSessionTabPending,
|
||||
startReviewSession,
|
||||
resumeReviewSession,
|
||||
startTestSession,
|
||||
resumeTestSession,
|
||||
@@ -375,6 +376,7 @@ export function App() {
|
||||
onOpenInBrowser={openUrl}
|
||||
onResumeSession={resumeSession}
|
||||
isSessionTabPending={isSessionTabPending}
|
||||
onStartReviewSession={startReviewSession}
|
||||
onResumeReviewSession={resumeReviewSession}
|
||||
onStartTestSession={startTestSession}
|
||||
onResumeTestSession={resumeTestSession}
|
||||
|
||||
@@ -31,6 +31,7 @@ interface BottomTabsProps {
|
||||
onResumeSession: (sessionId: string, kind: 'implement' | 'brainstorm', profilePath?: string, cwd?: string, issueNumber?: number) => void
|
||||
isSessionTabPending: (sessionId: string) => boolean
|
||||
onResumeReviewSession: (sessionId: string, issueNumber: number, cwd?: string) => void
|
||||
onStartReviewSession: (issueNumber: number) => void
|
||||
onStartTestSession: (issueNumber: number) => void
|
||||
onResumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
|
||||
onOpenFile: (path: string) => void
|
||||
@@ -130,6 +131,7 @@ export function BottomTabs(props: BottomTabsProps) {
|
||||
onResumeSession={props.onResumeSession}
|
||||
isSessionTabPending={props.isSessionTabPending}
|
||||
onResumeReviewSession={props.onResumeReviewSession}
|
||||
onStartReviewSession={props.onStartReviewSession}
|
||||
onStartTestSession={props.onStartTestSession}
|
||||
onResumeTestSession={props.onResumeTestSession}
|
||||
onOpenFile={props.onOpenFile}
|
||||
|
||||
@@ -55,6 +55,7 @@ interface IssueDetailPanelProps {
|
||||
* Spawn a fresh "测试" cc tab for an issue whose PR has merged. Bound to the
|
||||
* Play button next to the "测试会话 id" row (only shown when `issue.pr` exists).
|
||||
*/
|
||||
onStartReviewSession: (issueNumber: number) => void
|
||||
onStartTestSession: (issueNumber: number) => void
|
||||
/** Open a workspace-relative file in the editor. */
|
||||
onOpenFile: (path: string) => void
|
||||
@@ -133,6 +134,7 @@ export function IssueDetailPanel({
|
||||
onResumeSession,
|
||||
onResumeReviewSession,
|
||||
onResumeTestSession,
|
||||
onStartReviewSession,
|
||||
onStartTestSession,
|
||||
onOpenFile,
|
||||
onImplement,
|
||||
@@ -290,13 +292,27 @@ export function IssueDetailPanel({
|
||||
return
|
||||
onResumeSession(v, 'implement', issue?.profilePath, issue?.worktreePath, issue?.number)
|
||||
},
|
||||
secondaryActions: issue?.implementTabOpen
|
||||
? [{
|
||||
secondaryActions: (() => {
|
||||
if (issue?.implementTabOpen) {
|
||||
return [{
|
||||
icon: <X className="size-3.5" />,
|
||||
title: '关闭实施 tab',
|
||||
onClick: () => onCloseSessionTab(issue.number, 'implement'),
|
||||
}]
|
||||
: [],
|
||||
}
|
||||
// 还没实施会话时给个 ▶,省去把卡片拖进「实施中」列。
|
||||
if (issue?.implementSessionId || !issue?.planFile)
|
||||
return []
|
||||
return [{
|
||||
icon: <Play className="size-3.5" />,
|
||||
title: locked ? lockTitle : '启动实施会话',
|
||||
disabled:
|
||||
issue.implementStatus === 'running'
|
||||
|| issue.implementStatus === 'done'
|
||||
|| locked,
|
||||
onClick: () => onImplement(issue.number, issue.planFile ?? '', issue.profilePath, issue.sessionId),
|
||||
}]
|
||||
})(),
|
||||
},
|
||||
issue?.reviewSessionFileExists
|
||||
? {
|
||||
@@ -324,9 +340,19 @@ export function IssueDetailPanel({
|
||||
: {
|
||||
key: 'reviewSessionId',
|
||||
label: '审查会话id',
|
||||
type: 'string',
|
||||
type: 'action',
|
||||
readOnly: true,
|
||||
description: '本机没有该 codex 会话文件,无法 resume;仅保留 id 文本',
|
||||
description: issue?.reviewSessionId
|
||||
? '本机没有该 codex 会话文件,无法 resume;点 ▶ 对当前 PR 重新起一个审查会话'
|
||||
: '还没有审查会话;点 ▶ 对当前 PR 启动 codex 审查(等同 PR 事件自动触发的那次)',
|
||||
secondaryActions: issue?.pr
|
||||
? [{
|
||||
icon: <Play className="size-3.5" />,
|
||||
title: locked ? lockTitle : '启动审查会话',
|
||||
disabled: locked,
|
||||
onClick: () => onStartReviewSession(issue.number),
|
||||
}]
|
||||
: [],
|
||||
},
|
||||
(() => {
|
||||
// 测试会话 id 行有三种状态(照搬头脑风暴行的三态):
|
||||
@@ -542,7 +568,7 @@ export function IssueDetailPanel({
|
||||
],
|
||||
},
|
||||
],
|
||||
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onOpenFile, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onClearBrainstormSession, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles, isSessionTabPending],
|
||||
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onOpenFile, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onClearBrainstormSession, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles, isSessionTabPending, onStartReviewSession],
|
||||
)
|
||||
|
||||
// 同事的工单只读:属性表统一消毒,顶部只留查看类按钮。
|
||||
|
||||
@@ -187,6 +187,7 @@ export interface UseIssuesResult {
|
||||
/** 该会话 id 的开 tab 请求还没被扩展处理完 → 行内转 spinner。 */
|
||||
isSessionTabPending: (sessionId: string) => boolean
|
||||
resumeReviewSession: (sessionId: string, issueNumber: number, cwd?: string) => void
|
||||
startReviewSession: (issueNumber: number) => void
|
||||
startTestSession: (issueNumber: number) => void
|
||||
resumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
|
||||
focusSession: (issueNumber: number) => void
|
||||
@@ -545,6 +546,10 @@ export function useIssues(): UseIssuesResult {
|
||||
postMessage({ type: 'session/resume-review', sessionId, issueNumber, cwd })
|
||||
}, [markSessionTabPending])
|
||||
|
||||
const startReviewSession = useCallback((issueNumber: number): void => {
|
||||
postMessage({ type: 'session/start-review', issueNumber })
|
||||
}, [])
|
||||
|
||||
const startTestSession = useCallback((issueNumber: number): void => {
|
||||
postMessage({ type: 'session/start-test', issueNumber })
|
||||
}, [])
|
||||
@@ -990,5 +995,5 @@ export function useIssues(): UseIssuesResult {
|
||||
return cleanup
|
||||
}, [clearPrDiffSummaryRunning, markHandoffRunning])
|
||||
|
||||
return { state, settings, avatars, isSessionTabPending, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startTestSession, resumeTestSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearBrainstormSession, updateIssueTestProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
|
||||
return { state, settings, avatars, isSessionTabPending, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startReviewSession, startTestSession, resumeTestSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearBrainstormSession, updateIssueTestProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
|
||||
}
|
||||
@@ -224,6 +224,7 @@ export type WebviewToExtension
|
||||
| { type: 'session/focus', issueNumber: number }
|
||||
| { type: 'session/resume-review', sessionId: string, issueNumber: number, cwd?: string }
|
||||
| { type: 'session/resume-test', sessionId: string, issueNumber: number, cwd?: string }
|
||||
| { type: 'session/start-review', issueNumber: number }
|
||||
| { type: 'session/start-test', issueNumber: number }
|
||||
| { type: 'editor/open-file', path: string }
|
||||
| { type: 'profiles/list' }
|
||||
|
||||
Reference in new issue
Block a user