✨ 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:
cruldra committed 2026-09-14 10:20:30 +08:00
1 parent 3fff796a61
commit b62e58d739
27 files changed
+760 -50

No files matched your search

+8
View File
@@ -45,6 +45,8 @@ export function App() {
resumeReviewSession,
startTestSession,
resumeTestSession,
startPrototypeSession,
resumePrototypeSession,
focusSession,
openFile,
implement,
@@ -66,6 +68,7 @@ export function App() {
updateIssueProfilePath,
updateIssueBrainstormProfilePath,
updateIssueTestProfilePath,
updateIssuePrototypeProfilePath,
logs,
clearLogs,
pendingSelectId,
@@ -380,6 +383,8 @@ export function App() {
onResumeReviewSession={resumeReviewSession}
onStartTestSession={startTestSession}
onResumeTestSession={resumeTestSession}
onStartPrototypeSession={startPrototypeSession}
onResumePrototypeSession={resumePrototypeSession}
onOpenFile={openFile}
onImplement={implement}
onOpenPr={openPr}
@@ -398,6 +403,7 @@ export function App() {
onUpdateProfilePath={updateIssueProfilePath}
onUpdateBrainstormProfilePath={updateIssueBrainstormProfilePath}
onUpdateTestProfilePath={updateIssueTestProfilePath}
onUpdatePrototypeProfilePath={updateIssuePrototypeProfilePath}
onOpenLogs={() => setShowLogs(true)}
me={me}
avatars={avatars}
@@ -539,6 +545,7 @@ export function App() {
initialAutoReview={settings?.autoReview ?? true}
initialReviewPrompt={settings?.reviewPrompt ?? ''}
initialTestPrompt={settings?.testPrompt ?? ''}
initialPrototypePrompt={settings?.prototypePrompt ?? ''}
initialDevBranch={settings?.devBranch ?? 'main'}
initialReleaseBranchPrefix={settings?.releaseBranchPrefix ?? 'release/'}
initialJenkinsJob={settings?.jenkinsJob ?? ''}
@@ -554,6 +561,7 @@ export function App() {
initialDefaultBrainstormProfilePath={settings?.defaultBrainstormProfilePath ?? ''}
initialDefaultImplementProfilePath={settings?.defaultImplementProfilePath ?? ''}
initialDefaultTestProfilePath={settings?.defaultTestProfilePath ?? ''}
initialDefaultPrototypeProfilePath={settings?.defaultPrototypeProfilePath ?? ''}
initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''}
initialCommitProfilePath={settings?.commitProfilePath ?? ''}
initialPrDiffSummaryProfilePath={settings?.prDiffSummaryProfilePath ?? ''}
@@ -34,6 +34,8 @@ interface BottomTabsProps {
onStartReviewSession: (issueNumber: number) => void
onStartTestSession: (issueNumber: number) => void
onResumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
onStartPrototypeSession: (issueNumber: number) => void
onResumePrototypeSession: (sessionId: string, issueNumber: number, cwd?: string) => void
onOpenFile: (path: string) => void
onImplement: (issueNumber: number, planFile: string, profilePath?: string, sessionId?: string) => void
onOpenPr: (pr: string) => void
@@ -45,13 +47,14 @@ interface BottomTabsProps {
onDeleteIssue?: (issueNumber: number) => void
onCloseIssue: (issueNumber: number) => void
onResetToTodo: (issueNumber: number) => void
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test') => void
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
onContinueBrainstorm: (issueNumber: number) => void
onClearBrainstormSession: (issueNumber: number) => void
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
onUpdateProfilePath: (issueNumber: number, profilePath: string) => void
onUpdateBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void
onUpdateTestProfilePath: (issueNumber: number, testProfilePath: string) => void
onUpdatePrototypeProfilePath: (issueNumber: number, prototypeProfilePath: string) => void
onOpenLogs: () => void
me?: string
/** Gitea login → 头像 data URI。 */
@@ -134,6 +137,8 @@ export function BottomTabs(props: BottomTabsProps) {
onStartReviewSession={props.onStartReviewSession}
onStartTestSession={props.onStartTestSession}
onResumeTestSession={props.onResumeTestSession}
onStartPrototypeSession={props.onStartPrototypeSession}
onResumePrototypeSession={props.onResumePrototypeSession}
onOpenFile={props.onOpenFile}
onImplement={props.onImplement}
onOpenPr={props.onOpenPr}
@@ -152,6 +157,7 @@ export function BottomTabs(props: BottomTabsProps) {
onUpdateProfilePath={props.onUpdateProfilePath}
onUpdateBrainstormProfilePath={props.onUpdateBrainstormProfilePath}
onUpdateTestProfilePath={props.onUpdateTestProfilePath}
onUpdatePrototypeProfilePath={props.onUpdatePrototypeProfilePath}
profiles={props.profiles}
onOpenLogs={props.onOpenLogs}
me={props.me}
@@ -51,12 +51,22 @@ interface IssueDetailPanelProps {
* conversation associated with this issue.
*/
onResumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
/**
* Open a new terminal that runs `claude --resume <id>` for the prototype
* conversation associated with this issue.
*/
onResumePrototypeSession: (sessionId: string, issueNumber: number, cwd?: string) => void
/**
* 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
/**
* Spawn a fresh "原型" cc tab. Bound to the Play button next to the
* "原型会话 id" row;不依赖 PR,任何阶段都能起。
*/
onStartPrototypeSession: (issueNumber: number) => void
/** Open a workspace-relative file in the editor. */
onOpenFile: (path: string) => void
/** Kick off the end-to-end implementation flow for the given plan file. */
@@ -90,7 +100,7 @@ interface IssueDetailPanelProps {
* onDidCloseTerminal and clears the corresponding `*TabOpen` flag, which
* makes the X button vanish on its own.
*/
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test') => void
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
/**
* 「▶ 续聊」:由插件把续聊提示词送进头脑风暴会话——规划 tab 开着就直接
* 打进去,关了就 `claude --resume <id>` 带着提示词恢复,没会话就新开一个。
@@ -107,6 +117,8 @@ interface IssueDetailPanelProps {
onUpdateBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void
/** Persist a per-issue `testProfilePath` override into the state JSON. */
onUpdateTestProfilePath: (issueNumber: number, testProfilePath: string) => void
/** Persist a per-issue `prototypeProfilePath` override into the state JSON. */
onUpdatePrototypeProfilePath: (issueNumber: number, prototypeProfilePath: string) => void
/** Open the in-webview log modal. */
onOpenLogs: () => void
/** 当前 Gitea login;undefined = 身份未知(接管按钮不显示)。 */
@@ -134,8 +146,10 @@ export function IssueDetailPanel({
onResumeSession,
onResumeReviewSession,
onResumeTestSession,
onResumePrototypeSession,
onStartReviewSession,
onStartTestSession,
onStartPrototypeSession,
onOpenFile,
onImplement,
onOpenPr,
@@ -155,6 +169,7 @@ export function IssueDetailPanel({
onUpdateProfilePath,
onUpdateBrainstormProfilePath,
onUpdateTestProfilePath,
onUpdatePrototypeProfilePath,
onOpenLogs,
me,
isSessionTabPending,
@@ -402,6 +417,51 @@ export function IssueDetailPanel({
secondaryActions,
}
})(),
(() => {
// 原型会话 id 行:与测试行同构的三态,差别是启动不看 PR——原型只照
// 工单正文画界面,任何阶段都能起;前置工单没做完时按钮禁用。
const hasSession = !!(issue?.prototypeSessionId && issue.prototypeSessionId.length > 0)
const secondaryActions: RowAction[] = []
if (hasSession && issue?.prototypeTabOpen) {
secondaryActions.push({
icon: <X className="size-3.5" />,
title: '关闭原型 tab',
onClick: () => {
if (issue)
onCloseSessionTab(issue.number, 'prototype')
},
})
}
else if (!hasSession) {
secondaryActions.push({
icon: <Play className="size-3.5" />,
title: locked ? lockTitle : '启动原型会话',
disabled: locked,
onClick: () => {
if (issue)
onStartPrototypeSession(issue.number)
},
})
}
return {
key: 'prototypeSessionId',
label: '原型会话id',
type: 'action' as const,
pending: hasSession ? isSessionTabPending(issue!.prototypeSessionId!) : false,
description: hasSession
? '双击在新终端运行 claude --resume <id> 恢复原型对话(worktree 在则用之,否则工作区根目录)'
: '点击右侧 ▶ 启动一个原型 cc tab:照工单正文画界面原型,不依赖 PR',
actionIcon: <Terminal className="size-3.5" />,
onAction: (v: unknown) => {
if (typeof v !== 'string' || v.length === 0 || !issue)
return
if (!throttleBySessionId(v))
return
onResumePrototypeSession(v, issue.number)
},
secondaryActions,
}
})(),
{
key: 'autoReview',
label: '自动审查',
@@ -458,6 +518,19 @@ export function IssueDetailPanel({
})(),
description: '测试会话使用的 Claude 配置文件;选「(默认)」用内置默认,与头脑风暴/实施互不回退',
},
{
key: 'prototypeProfilePath',
label: '原型配置文件',
type: 'select',
options: (() => {
const opts = [{ label: '(默认)', value: '' }, ...profiles.map(p => ({ label: p.name, value: p.path }))]
const current = matchProfileOption(issue?.prototypeProfilePath, profiles)
if (current && !opts.some(o => o.value === current))
opts.push({ label: `自定义:${current}`, value: current })
return opts
})(),
description: '原型会话使用的 Claude 配置文件;选「(默认)」用内置默认,与其它会话互不回退',
},
{
key: 'specFile',
label: '规格文件',
@@ -568,7 +641,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, onStartReviewSession],
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onResumePrototypeSession, onStartPrototypeSession, onOpenFile, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onClearBrainstormSession, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles, isSessionTabPending, onStartReviewSession],
)
// 同事的工单只读:属性表统一消毒,顶部只留查看类按钮。
@@ -588,12 +661,14 @@ export function IssueDetailPanel({
implementSessionId: issue.implementSessionId ?? null,
reviewSessionId: issue.reviewSessionId ?? null,
testSessionId: issue.testSessionId ?? null,
prototypeSessionId: issue.prototypeSessionId ?? null,
autoReview: issue.autoReview ?? globalAutoReview,
color: issue.color ?? null,
// 展示/选中值按 basename 映射到本机 profiles,避免跨机绝对路径变成「自定义」项。
brainstormProfilePath: matchProfileOption(issue.brainstormProfilePath, profiles) || null,
profilePath: matchProfileOption(issue.profilePath, profiles) || null,
testProfilePath: matchProfileOption(issue.testProfilePath, profiles) || null,
prototypeProfilePath: matchProfileOption(issue.prototypeProfilePath, profiles) || null,
specFile: issue.specFile ?? null,
planFile: issue.planFile ?? null,
prDiffFile: issue.prDiffFile ?? null,
@@ -788,6 +863,8 @@ export function IssueDetailPanel({
onUpdateProfilePath(issue.number, value)
if (key === 'testProfilePath' && issue && typeof value === 'string')
onUpdateTestProfilePath(issue.number, value)
if (key === 'prototypeProfilePath' && issue && typeof value === 'string')
onUpdatePrototypeProfilePath(issue.number, value)
}}
/>
</div>
@@ -26,6 +26,7 @@ interface SubmitValues {
autoReview: boolean
reviewPrompt: string
testPrompt: string
prototypePrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -41,6 +42,7 @@ interface SubmitValues {
defaultBrainstormProfilePath: string
defaultImplementProfilePath: string
defaultTestProfilePath: string
defaultPrototypeProfilePath: string
conflictResolutionProfilePath: string
commitProfilePath: string
prDiffSummaryProfilePath: string
@@ -80,6 +82,7 @@ export interface SettingsModalProps {
initialAutoReview: boolean
initialReviewPrompt: string
initialTestPrompt: string
initialPrototypePrompt: string
initialDevBranch: string
initialReleaseBranchPrefix: string
initialJenkinsJob: string
@@ -95,6 +98,7 @@ export interface SettingsModalProps {
initialDefaultBrainstormProfilePath: string
initialDefaultImplementProfilePath: string
initialDefaultTestProfilePath: string
initialDefaultPrototypeProfilePath: string
initialConflictResolutionProfilePath: string
initialCommitProfilePath: string
initialPrDiffSummaryProfilePath: string
@@ -170,6 +174,7 @@ export function SettingsModal({
initialAutoReview,
initialReviewPrompt,
initialTestPrompt,
initialPrototypePrompt,
initialDevBranch,
initialReleaseBranchPrefix,
initialJenkinsJob,
@@ -185,6 +190,7 @@ export function SettingsModal({
initialDefaultBrainstormProfilePath,
initialDefaultImplementProfilePath,
initialDefaultTestProfilePath,
initialDefaultPrototypeProfilePath,
initialConflictResolutionProfilePath,
initialCommitProfilePath,
initialPrDiffSummaryProfilePath,
@@ -212,6 +218,7 @@ export function SettingsModal({
const [autoReview, setAutoReview] = useState(initialAutoReview)
const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt)
const [testPrompt, setTestPrompt] = useState(initialTestPrompt)
const [prototypePrompt, setPrototypePrompt] = useState(initialPrototypePrompt)
const [devBranch, setDevBranch] = useState(initialDevBranch)
const [releaseBranchPrefix, setReleaseBranchPrefix] = useState(initialReleaseBranchPrefix)
const [jenkinsJob, setJenkinsJob] = useState(initialJenkinsJob)
@@ -227,6 +234,7 @@ export function SettingsModal({
const [defaultBrainstormProfilePath, setDefaultBrainstormProfilePath] = useState(initialDefaultBrainstormProfilePath)
const [defaultImplementProfilePath, setDefaultImplementProfilePath] = useState(initialDefaultImplementProfilePath)
const [defaultTestProfilePath, setDefaultTestProfilePath] = useState(initialDefaultTestProfilePath)
const [defaultPrototypeProfilePath, setDefaultPrototypeProfilePath] = useState(initialDefaultPrototypeProfilePath)
const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath)
const [commitProfilePath, setCommitProfilePath] = useState(initialCommitProfilePath)
const [prDiffSummaryProfilePath, setPrDiffSummaryProfilePath] = useState(initialPrDiffSummaryProfilePath)
@@ -256,6 +264,7 @@ export function SettingsModal({
setAutoReview(initialAutoReview)
setReviewPrompt(initialReviewPrompt)
setTestPrompt(initialTestPrompt)
setPrototypePrompt(initialPrototypePrompt)
setDevBranch(initialDevBranch)
setReleaseBranchPrefix(initialReleaseBranchPrefix)
setJenkinsJob(initialJenkinsJob)
@@ -271,6 +280,7 @@ export function SettingsModal({
setDefaultBrainstormProfilePath(initialDefaultBrainstormProfilePath)
setDefaultImplementProfilePath(initialDefaultImplementProfilePath)
setDefaultTestProfilePath(initialDefaultTestProfilePath)
setDefaultPrototypeProfilePath(initialDefaultPrototypeProfilePath)
setConflictResolutionProfilePath(initialConflictResolutionProfilePath)
setCommitProfilePath(initialCommitProfilePath)
setPrDiffSummaryProfilePath(initialPrDiffSummaryProfilePath)
@@ -348,6 +358,7 @@ export function SettingsModal({
autoReview,
reviewPrompt,
testPrompt,
prototypePrompt,
devBranch: devBranch.trim(),
releaseBranchPrefix: releaseBranchPrefix.trim(),
jenkinsJob: jenkinsJob.trim(),
@@ -363,10 +374,11 @@ export function SettingsModal({
implTabPreCreateScript: implTabPreCreateScript.trim(),
implTabPostCloseScript: implTabPostCloseScript.trim(),
splitNewTerminals,
// 三个会话默认 profile:空串 = 落到 DEFAULT_PROFILE_PATH
// 四个会话默认 profile:空串 = 落到 DEFAULT_PROFILE_PATH
defaultBrainstormProfilePath: defaultBrainstormProfilePath.trim(),
defaultImplementProfilePath: defaultImplementProfilePath.trim(),
defaultTestProfilePath: defaultTestProfilePath.trim(),
defaultPrototypeProfilePath: defaultPrototypeProfilePath.trim(),
// 冲突解决 profile:空串 = 用 DEFAULT_PROFILE_PATH
conflictResolutionProfilePath: conflictResolutionProfilePath.trim(),
// 提交代码 profile:空串 = 未配置 → 提交严格失败
@@ -625,6 +637,30 @@ export function SettingsModal({
/>
</Field>
<Field
label="原型默认配置文件"
hint={(
<>
原型会话默认用此 profile;工单详情面板里的「原型配置文件」优先级更高。留空用 official。
</>
)}
>
<SelectMenu
value={defaultPrototypeProfilePath}
onChange={setDefaultPrototypeProfilePath}
variant="input"
className={inputClass}
options={[
{ value: '', label: '(默认)' },
...profiles.map(p => ({ value: p.path, label: p.name })),
...(defaultPrototypeProfilePath
&& !profiles.some(p => p.path === defaultPrototypeProfilePath)
? [{ value: defaultPrototypeProfilePath, label: `自定义:${defaultPrototypeProfilePath}` }]
: []),
]}
/>
</Field>
<Field
label="冲突解决配置文件"
hint={(
@@ -1028,6 +1064,23 @@ export function SettingsModal({
className={`${inputClass} resize-y font-mono`}
/>
</Field>
<Field
label="原型提示词"
hint={(
<>
可用占位符:
<code>{'{issueNumber}'}</code>
</>
)}
>
<textarea
rows={4}
value={prototypePrompt}
onChange={e => setPrototypePrompt(e.target.value)}
className={`${inputClass} resize-y font-mono`}
/>
</Field>
</>
)}
+41 -3
View File
@@ -44,6 +44,7 @@ export interface SettingsValues {
autoReview: boolean
reviewPrompt: string
testPrompt: string
prototypePrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -59,6 +60,7 @@ export interface SettingsValues {
defaultBrainstormProfilePath: string
defaultImplementProfilePath: string
defaultTestProfilePath: string
defaultPrototypeProfilePath: string
conflictResolutionProfilePath: string
commitProfilePath: string
prDiffSummaryProfilePath: string
@@ -84,6 +86,7 @@ export interface SettingsOverlayState {
autoReview: boolean
reviewPrompt: string
testPrompt: string
prototypePrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -99,6 +102,7 @@ export interface SettingsOverlayState {
defaultBrainstormProfilePath: string
defaultImplementProfilePath: string
defaultTestProfilePath: string
defaultPrototypeProfilePath: string
conflictResolutionProfilePath: string
commitProfilePath: string
prDiffSummaryProfilePath: string
@@ -192,6 +196,8 @@ export interface UseIssuesResult {
startReviewSession: (issueNumber: number) => void
startTestSession: (issueNumber: number) => void
resumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
startPrototypeSession: (issueNumber: number) => void
resumePrototypeSession: (sessionId: string, issueNumber: number, cwd?: string) => void
focusSession: (issueNumber: number) => void
openFile: (path: string) => void
implement: (issueNumber: number, planFile: string, profilePath?: string, sessionId?: string) => void
@@ -213,7 +219,7 @@ export interface UseIssuesResult {
* Dispose the matching session terminal tab. Extension reacts via
* onDidCloseTerminal and clears `*TabOpen` so the X button disappears.
*/
closeSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test') => void
closeSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
/**
* Spawn a fresh "规划" cc tab for an existing issue whose sessionId is
* still empty. Extension 用工单级 brainstormProfilePath(空 = 默认),
@@ -242,6 +248,11 @@ export interface UseIssuesResult {
* postMessage 持久化到 state JSON。失败时扩展端 push `issue/patch` 回滚。
*/
updateIssueTestProfilePath: (issueNumber: number, testProfilePath: string) => void
/**
* 覆盖该工单原型会话专用的 Claude 配置文件,乐观更新本地 state 后
* postMessage 持久化到 state JSON。失败时扩展端 push `issue/patch` 回滚。
*/
updateIssuePrototypeProfilePath: (issueNumber: number, prototypeProfilePath: string) => void
logs: LogEntry[]
fetchLogs: () => void
clearLogs: () => void
@@ -424,6 +435,7 @@ export function useIssues(): UseIssuesResult {
autoReview: values.autoReview,
reviewPrompt: values.reviewPrompt,
testPrompt: values.testPrompt,
prototypePrompt: values.prototypePrompt,
devBranch: values.devBranch,
releaseBranchPrefix: values.releaseBranchPrefix,
jenkinsJob: values.jenkinsJob,
@@ -439,6 +451,7 @@ export function useIssues(): UseIssuesResult {
defaultBrainstormProfilePath: values.defaultBrainstormProfilePath,
defaultImplementProfilePath: values.defaultImplementProfilePath,
defaultTestProfilePath: values.defaultTestProfilePath,
defaultPrototypeProfilePath: values.defaultPrototypeProfilePath,
conflictResolutionProfilePath: values.conflictResolutionProfilePath,
commitProfilePath: values.commitProfilePath,
prDiffSummaryProfilePath: values.prDiffSummaryProfilePath,
@@ -562,6 +575,15 @@ export function useIssues(): UseIssuesResult {
postMessage({ type: 'session/resume-test', sessionId, issueNumber, cwd })
}, [markSessionTabPending])
const startPrototypeSession = useCallback((issueNumber: number): void => {
postMessage({ type: 'session/start-prototype', issueNumber })
}, [])
const resumePrototypeSession = useCallback((sessionId: string, issueNumber: number, cwd?: string): void => {
markSessionTabPending(sessionId)
postMessage({ type: 'session/resume-prototype', sessionId, issueNumber, cwd })
}, [markSessionTabPending])
const openFile = useCallback((path: string): void => {
postMessage({ type: 'editor/open-file', path })
}, [])
@@ -641,7 +663,7 @@ export function useIssues(): UseIssuesResult {
}, [])
const closeSessionTab = useCallback(
(issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test'): void => {
(issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype'): void => {
postMessage({ type: 'session/close-tab', issueNumber, kind })
},
[],
@@ -734,6 +756,20 @@ export function useIssues(): UseIssuesResult {
postMessage({ type: 'issue/update-test-profile-path', issueNumber, testProfilePath })
}, [])
const updateIssuePrototypeProfilePath = useCallback((issueNumber: number, prototypeProfilePath: string): void => {
setState((prev) => {
if (prev.status !== 'ready')
return prev
return {
status: 'ready',
issues: prev.issues.map(i =>
i.number === issueNumber ? { ...i, prototypeProfilePath } : i,
),
}
})
postMessage({ type: 'issue/update-prototype-profile-path', issueNumber, prototypeProfilePath })
}, [])
const fetchLogs = useCallback((): void => {
postMessage({ type: 'logs/fetch' })
}, [])
@@ -890,6 +926,7 @@ export function useIssues(): UseIssuesResult {
autoReview: msg.autoReview,
reviewPrompt: msg.reviewPrompt,
testPrompt: msg.testPrompt,
prototypePrompt: msg.prototypePrompt,
devBranch: msg.devBranch,
releaseBranchPrefix: msg.releaseBranchPrefix,
jenkinsJob: msg.jenkinsJob,
@@ -905,6 +942,7 @@ export function useIssues(): UseIssuesResult {
defaultBrainstormProfilePath: msg.defaultBrainstormProfilePath,
defaultImplementProfilePath: msg.defaultImplementProfilePath,
defaultTestProfilePath: msg.defaultTestProfilePath,
defaultPrototypeProfilePath: msg.defaultPrototypeProfilePath,
conflictResolutionProfilePath: msg.conflictResolutionProfilePath,
commitProfilePath: msg.commitProfilePath,
prDiffSummaryProfilePath: msg.prDiffSummaryProfilePath,
@@ -999,5 +1037,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, 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 }
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, startPrototypeSession, resumePrototypeSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearBrainstormSession, updateIssueTestProfilePath, updateIssuePrototypeProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
}
+9 -2
View File
@@ -83,7 +83,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, specFile?: string, planFile?: string, prDiffFile?: string | null, sessionId?: string | null, implementSessionId?: string | null, reviewSessionId?: string | null, reviewSessionFileExists?: boolean, testSessionId?: 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, profilePath?: string | null, brainstormProfilePath?: string | null, testProfilePath?: string | null, assignees?: string[], handoffAttachmentId?: string | null, handoffFrom?: string | null } }
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string | null, sessionId?: string | null, implementSessionId?: string | null, reviewSessionId?: string | null, reviewSessionFileExists?: boolean, 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 } }
/** 某个会话 id 的开 tab 请求已处理完(开成、复用或被拒都算),webview 据此停掉行内 spinner。 */
| { type: 'session/tab-settled', sessionId: string }
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
@@ -108,6 +108,7 @@ export type ExtensionToWebview
autoReview: boolean
reviewPrompt: string
testPrompt: string
prototypePrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -123,6 +124,7 @@ export type ExtensionToWebview
defaultBrainstormProfilePath: string
defaultImplementProfilePath: string
defaultTestProfilePath: string
defaultPrototypeProfilePath: string
conflictResolutionProfilePath: string
commitProfilePath: string
prDiffSummaryProfilePath: string
@@ -191,6 +193,7 @@ export type WebviewToExtension
autoReview: boolean
reviewPrompt: string
testPrompt: string
prototypePrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -206,6 +209,7 @@ export type WebviewToExtension
defaultBrainstormProfilePath: string
defaultImplementProfilePath: string
defaultTestProfilePath: string
defaultPrototypeProfilePath: string
conflictResolutionProfilePath: string
commitProfilePath: string
prDiffSummaryProfilePath: string
@@ -226,8 +230,10 @@ 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/resume-prototype', sessionId: string, issueNumber: number, cwd?: string }
| { type: 'session/start-review', issueNumber: number }
| { type: 'session/start-test', issueNumber: number }
| { type: 'session/start-prototype', issueNumber: number }
| { type: 'editor/open-file', path: string }
| { type: 'profiles/list' }
| { type: 'issue/implement', issueNumber: number, planFile: string, profilePath?: string, sessionId?: string }
@@ -251,10 +257,11 @@ export type WebviewToExtension
| { type: 'issue/update-brainstorm-profile-path', issueNumber: number, brainstormProfilePath: string }
| { type: 'issue/clear-brainstorm-session', issueNumber: number }
| { type: 'issue/update-test-profile-path', issueNumber: number, testProfilePath: string }
| { type: 'issue/update-prototype-profile-path', issueNumber: number, prototypeProfilePath: string }
| { type: 'logs/fetch' }
| { type: 'logs/clear' }
| { type: 'commit/run' }
| { type: 'session/close-tab', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' }
| { type: 'session/close-tab', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype' }
| { type: 'release/check' }
| { type: 'release/cut' }
| { type: 'release/cherry-pick' }
+10 -2
View File
@@ -26,6 +26,8 @@ export interface Issue {
brainstormProfilePath?: string
/** 测试会话专用的 Claude 配置文件;空 = 默认,不回退其它字段。 */
testProfilePath?: string
/** 原型会话专用的 Claude 配置文件;空 = 默认,不回退其它字段。 */
prototypeProfilePath?: string
/**
* Optional workspace-relative path to the spec file for this issue, as
* surfaced from the Claude session transcript. Lives under
@@ -71,6 +73,11 @@ export interface Issue {
* the PR is merged, resumable from the detail panel.
*/
testSessionId?: string
/**
* Claude Code session id of the prototype conversation; 与 PR 无关,
* 任何阶段都能从详情面板起,起完可 resume。
*/
prototypeSessionId?: string
htmlUrl: string
/** 前置任务 issue number (Gitea dependencies 取第一个) */
prerequisite?: number
@@ -90,13 +97,14 @@ export interface Issue {
/** 发起移交的 Gitea login。 */
handoffFrom?: string
/**
* 三类会话 tab 是否当前在编辑器分组里打开(由 extension 通过 issue/patch 同步)。
* 仅用于详情面板控制是否在三行会话 id 右侧显示 "关闭 tab" 按钮,不持久化到 state JSON。
* 各类会话 tab 是否当前在编辑器分组里打开(由 extension 通过 issue/patch 同步)。
* 仅用于详情面板控制是否在会话 id 行右侧显示 "关闭 tab" 按钮,不持久化到 state JSON。
*/
brainstormTabOpen?: boolean
implementTabOpen?: boolean
reviewTabOpen?: boolean
testTabOpen?: boolean
prototypeTabOpen?: boolean
}
export const COLUMN_ORDER: IssueColumn[] = ['todo', 'in-progress', 'review', 'done']