✨ feat(vscode): state JSON 新增 handoffAttachmentId/handoffFrom,Issue 透出 assignees
Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph
This commit is contained in:
@@ -408,6 +408,8 @@ var knownStateFields = map[string]struct{}{
|
|||||||
"implementStatus": {},
|
"implementStatus": {},
|
||||||
"color": {},
|
"color": {},
|
||||||
"autoReview": {},
|
"autoReview": {},
|
||||||
|
"handoffAttachmentId": {},
|
||||||
|
"handoffFrom": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStateComment returns the parsed map when body is a JSON object carrying
|
// parseStateComment returns the parsed map when body is a JSON object carrying
|
||||||
|
|||||||
@@ -101,6 +101,16 @@
|
|||||||
"autoReview": {
|
"autoReview": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "本工单是否启用自动审查(覆盖全局 autoReview 设置)。"
|
"description": "本工单是否启用自动审查(覆盖全局 autoReview 设置)。"
|
||||||
|
},
|
||||||
|
"handoffAttachmentId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "移交会话包在 Gitea 的附件 id;存在即「待接管」,接管完成后清空。",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"handoffFrom": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "发起移交的 Gitea login。",
|
||||||
|
"minLength": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,16 @@
|
|||||||
"autoReview": {
|
"autoReview": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "本工单是否启用自动审查(覆盖全局 autoReview 设置)。"
|
"description": "本工单是否启用自动审查(覆盖全局 autoReview 设置)。"
|
||||||
|
},
|
||||||
|
"handoffAttachmentId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "移交会话包在 Gitea 的附件 id;存在即「待接管」,接管完成后清空。",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"handoffFrom": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "发起移交的 Gitea login。",
|
||||||
|
"minLength": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ function parseColumnFromComments(comments: GiteaComment[]): {
|
|||||||
testSessionId?: string
|
testSessionId?: string
|
||||||
color?: string
|
color?: string
|
||||||
autoReview?: boolean
|
autoReview?: boolean
|
||||||
|
handoffAttachmentId?: string
|
||||||
|
handoffFrom?: string
|
||||||
} {
|
} {
|
||||||
if (comments.length === 0)
|
if (comments.length === 0)
|
||||||
return { column: null }
|
return { column: null }
|
||||||
@@ -132,6 +134,8 @@ function parseColumnFromComments(comments: GiteaComment[]): {
|
|||||||
testSessionId?: unknown
|
testSessionId?: unknown
|
||||||
color?: unknown
|
color?: unknown
|
||||||
autoReview?: unknown
|
autoReview?: unknown
|
||||||
|
handoffAttachmentId?: unknown
|
||||||
|
handoffFrom?: unknown
|
||||||
}
|
}
|
||||||
if (isIssueColumn(obj.column)) {
|
if (isIssueColumn(obj.column)) {
|
||||||
const sessionId = typeof obj.sessionId === 'string' && obj.sessionId.length > 0
|
const sessionId = typeof obj.sessionId === 'string' && obj.sessionId.length > 0
|
||||||
@@ -180,6 +184,12 @@ function parseColumnFromComments(comments: GiteaComment[]): {
|
|||||||
? obj.color
|
? obj.color
|
||||||
: undefined
|
: undefined
|
||||||
const autoReview = typeof obj.autoReview === 'boolean' ? obj.autoReview : undefined
|
const autoReview = typeof obj.autoReview === 'boolean' ? obj.autoReview : undefined
|
||||||
|
const handoffAttachmentId = typeof obj.handoffAttachmentId === 'string' && obj.handoffAttachmentId.length > 0
|
||||||
|
? obj.handoffAttachmentId
|
||||||
|
: undefined
|
||||||
|
const handoffFrom = typeof obj.handoffFrom === 'string' && obj.handoffFrom.length > 0
|
||||||
|
? obj.handoffFrom
|
||||||
|
: undefined
|
||||||
return {
|
return {
|
||||||
column: obj.column,
|
column: obj.column,
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -200,6 +210,8 @@ function parseColumnFromComments(comments: GiteaComment[]): {
|
|||||||
testSessionId,
|
testSessionId,
|
||||||
color,
|
color,
|
||||||
autoReview,
|
autoReview,
|
||||||
|
handoffAttachmentId,
|
||||||
|
handoffFrom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,6 +338,8 @@ async function buildIssue(opts: {
|
|||||||
testSessionId,
|
testSessionId,
|
||||||
color,
|
color,
|
||||||
autoReview,
|
autoReview,
|
||||||
|
handoffAttachmentId,
|
||||||
|
handoffFrom,
|
||||||
} = parseColumnFromComments(comments)
|
} = parseColumnFromComments(comments)
|
||||||
// 优先用 PR API 实时结果;失败时退回 state JSON 持久化值。
|
// 优先用 PR API 实时结果;失败时退回 state JSON 持久化值。
|
||||||
const prMerged = liveMerged !== undefined ? liveMerged : persistedMerged
|
const prMerged = liveMerged !== undefined ? liveMerged : persistedMerged
|
||||||
@@ -392,6 +406,9 @@ async function buildIssue(opts: {
|
|||||||
...(prerequisite !== undefined ? { prerequisite } : {}),
|
...(prerequisite !== undefined ? { prerequisite } : {}),
|
||||||
...(color ? { color } : {}),
|
...(color ? { color } : {}),
|
||||||
...(autoReview !== undefined ? { autoReview } : {}),
|
...(autoReview !== undefined ? { autoReview } : {}),
|
||||||
|
...(handoffAttachmentId ? { handoffAttachmentId } : {}),
|
||||||
|
...(handoffFrom ? { handoffFrom } : {}),
|
||||||
|
assignees: (issue.assignees ?? []).map(a => a.login),
|
||||||
htmlUrl: issue.html_url,
|
htmlUrl: issue.html_url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { listIssueComments, postIssueComment } from './api'
|
|||||||
* 普通文本评论、审查评论、Gitea 自动关联评论都不含这些字段,因此从尾往前
|
* 普通文本评论、审查评论、Gitea 自动关联评论都不含这些字段,因此从尾往前
|
||||||
* 扫描时会被跳过,避免它们插队后下一次 merge 从空状态开始丢历史。
|
* 扫描时会被跳过,避免它们插队后下一次 merge 从空状态开始丢历史。
|
||||||
*/
|
*/
|
||||||
const KNOWN_STATE_FIELDS = ['column', 'sessionId', 'implementSessionId', 'reviewSessionId', 'testSessionId', 'profilePath', 'brainstormProfilePath', 'testProfilePath', 'specFile', 'planFile', 'prDiffFile', 'pr', 'prMerged', 'prMergedAt', 'branch', 'worktreePath', 'implementStatus', 'color', 'autoReview'] as const
|
const KNOWN_STATE_FIELDS = ['column', 'sessionId', 'implementSessionId', 'reviewSessionId', 'testSessionId', 'profilePath', 'brainstormProfilePath', 'testProfilePath', 'specFile', 'planFile', 'prDiffFile', 'pr', 'prMerged', 'prMergedAt', 'branch', 'worktreePath', 'implementStatus', 'color', 'autoReview', 'handoffAttachmentId', 'handoffFrom'] as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* body 能 parse 成 object 且含至少一个已知 state 字段 → 认为是 state JSON。
|
* body 能 parse 成 object 且含至少一个已知 state 字段 → 认为是 state JSON。
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ export interface Issue {
|
|||||||
* webhook coordinator uses this value instead of the global flag. Stored
|
* webhook coordinator uses this value instead of the global flag. Stored
|
||||||
* in the issue's state-JSON comment so it survives reloads. */
|
* in the issue's state-JSON comment so it survives reloads. */
|
||||||
autoReview?: boolean
|
autoReview?: boolean
|
||||||
|
/** Gitea assignee login 列表;接管按钮只对 assignee 含当前登录者的工单显示。 */
|
||||||
|
assignees?: string[]
|
||||||
|
/** 移交会话包的 Gitea 附件 id(字符串);存在即「待接管」。 */
|
||||||
|
handoffAttachmentId?: string
|
||||||
|
/** 发起移交的 Gitea login。 */
|
||||||
|
handoffFrom?: string
|
||||||
/** Whether the brainstorm terminal tab is currently alive in this VS Code window. */
|
/** Whether the brainstorm terminal tab is currently alive in this VS Code window. */
|
||||||
brainstormTabOpen?: boolean
|
brainstormTabOpen?: boolean
|
||||||
/** Whether the implementation terminal tab is currently alive in this VS Code window. */
|
/** Whether the implementation terminal tab is currently alive in this VS Code window. */
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { randomBytes } from 'node:crypto'
|
|||||||
import * as fs from 'node:fs'
|
import * as fs from 'node:fs'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import { env, TabInputTerminal, ThemeColor, Uri, ViewColumn, window, workspace } from 'vscode'
|
import { env, TabInputTerminal, ThemeColor, Uri, ViewColumn, window, workspace } from 'vscode'
|
||||||
|
import { loginForToken } from '../auth/identity'
|
||||||
import { deleteToken, getToken } from '../auth/secrets'
|
import { deleteToken, getToken } from '../auth/secrets'
|
||||||
import { detectRepo } from '../git/remote'
|
import { detectRepo } from '../git/remote'
|
||||||
import {
|
import {
|
||||||
@@ -882,12 +883,14 @@ export class KanbanWebviewPanel {
|
|||||||
issues.map(i => [i.number, { source: i.source ?? 'gitea', externalId: i.externalId }] as const),
|
issues.map(i => [i.number, { source: i.source ?? 'gitea', externalId: i.externalId }] as const),
|
||||||
)
|
)
|
||||||
const ytSettings = getSettings(this.context)
|
const ytSettings = getSettings(this.context)
|
||||||
|
const me = await loginForToken(host, token)
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
type: 'issues/update',
|
type: 'issues/update',
|
||||||
issues,
|
issues,
|
||||||
scope,
|
scope,
|
||||||
globalAutoReview: ytSettings.autoReview,
|
globalAutoReview: ytSettings.autoReview,
|
||||||
youtrackConfigured: ytSettings.youtrackBaseUrl.trim() !== '' && ytSettings.youtrackProjectShortName.trim() !== '',
|
youtrackConfigured: ytSettings.youtrackBaseUrl.trim() !== '' && ytSettings.youtrackProjectShortName.trim() !== '',
|
||||||
|
me,
|
||||||
})
|
})
|
||||||
// Re-publish the cached git state so a slow webview boot (or a
|
// Re-publish the cached git state so a slow webview boot (or a
|
||||||
// refresh after git events already fired) still picks up the latest
|
// refresh after git events already fired) still picks up the latest
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ export type ToastLevel = 'info' | 'success' | 'error'
|
|||||||
|
|
||||||
export type ExtensionToWebview
|
export type ExtensionToWebview
|
||||||
= | { type: 'issues/loading' }
|
= | { type: 'issues/loading' }
|
||||||
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean }
|
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean, me?: string }
|
||||||
| { type: 'issues/error', message: string }
|
| { type: 'issues/error', message: string }
|
||||||
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string, sessionId?: string, implementSessionId?: string | null, reviewSessionId?: string | null, reviewSessionFileExists?: boolean, testSessionId?: string, 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, brainstormProfilePath?: string, testProfilePath?: string } }
|
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string, 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, brainstormProfilePath?: string, testProfilePath?: string, assignees?: string[], handoffAttachmentId?: string | null, handoffFrom?: string | null } }
|
||||||
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
|
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
|
||||||
| { type: 'issue/append', issue: Issue, select?: boolean }
|
| { type: 'issue/append', issue: Issue, select?: boolean }
|
||||||
| { type: 'issue/select-by-number', issueNumber: number }
|
| { type: 'issue/select-by-number', issueNumber: number }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { addComment, listComments } from './api'
|
|||||||
export const STATE_MARKER = '<!--spx-state-->'
|
export const STATE_MARKER = '<!--spx-state-->'
|
||||||
|
|
||||||
/** Same field set as the Gitea state blob — kept independent to avoid coupling. */
|
/** Same field set as the Gitea state blob — kept independent to avoid coupling. */
|
||||||
const KNOWN_STATE_FIELDS = ['column', 'sessionId', 'implementSessionId', 'reviewSessionId', 'testSessionId', 'profilePath', 'brainstormProfilePath', 'testProfilePath', 'specFile', 'planFile', 'prDiffFile', 'pr', 'prMerged', 'prMergedAt', 'branch', 'worktreePath', 'implementStatus', 'color', 'autoReview'] as const
|
const KNOWN_STATE_FIELDS = ['column', 'sessionId', 'implementSessionId', 'reviewSessionId', 'testSessionId', 'profilePath', 'brainstormProfilePath', 'testProfilePath', 'specFile', 'planFile', 'prDiffFile', 'pr', 'prMerged', 'prMergedAt', 'branch', 'worktreePath', 'implementStatus', 'color', 'autoReview', 'handoffAttachmentId', 'handoffFrom'] as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a comment body into a state object, or null if it isn't a state
|
* Parse a comment body into a state object, or null if it isn't a state
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ export interface ProfilesData {
|
|||||||
|
|
||||||
export type ExtensionToWebview
|
export type ExtensionToWebview
|
||||||
= | { type: 'issues/loading' }
|
= | { type: 'issues/loading' }
|
||||||
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean }
|
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean, me?: string }
|
||||||
| { type: 'issues/error', message: string }
|
| { type: 'issues/error', message: string }
|
||||||
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string, sessionId?: string, implementSessionId?: string | null, reviewSessionId?: string | null, reviewSessionFileExists?: boolean, testSessionId?: string, 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, brainstormProfilePath?: string, testProfilePath?: string } }
|
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string, 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, brainstormProfilePath?: string, testProfilePath?: string, assignees?: string[], handoffAttachmentId?: string | null, handoffFrom?: string | null } }
|
||||||
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
|
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
|
||||||
| { type: 'issue/append', issue: Issue, select?: boolean }
|
| { type: 'issue/append', issue: Issue, select?: boolean }
|
||||||
| { type: 'issue/select-by-number', issueNumber: number }
|
| { type: 'issue/select-by-number', issueNumber: number }
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export interface Issue {
|
|||||||
/** Per-issue override of the global `autoReview` setting. Undefined = follow
|
/** Per-issue override of the global `autoReview` setting. Undefined = follow
|
||||||
* global setting; true/false = explicit override stored in state JSON. */
|
* global setting; true/false = explicit override stored in state JSON. */
|
||||||
autoReview?: boolean
|
autoReview?: boolean
|
||||||
|
/** Gitea assignee login 列表;接管按钮只对 assignee 含当前登录者的工单显示。 */
|
||||||
|
assignees?: string[]
|
||||||
|
/** 移交会话包的 Gitea 附件 id(字符串);存在即「待接管」。 */
|
||||||
|
handoffAttachmentId?: string
|
||||||
|
/** 发起移交的 Gitea login。 */
|
||||||
|
handoffFrom?: string
|
||||||
/** 三类会话 tab 是否当前在编辑器分组里打开(由 extension 通过 issue/patch 同步)。
|
/** 三类会话 tab 是否当前在编辑器分组里打开(由 extension 通过 issue/patch 同步)。
|
||||||
* 仅用于详情面板控制是否在三行会话 id 右侧显示 "关闭 tab" 按钮,不持久化到 state JSON。 */
|
* 仅用于详情面板控制是否在三行会话 id 右侧显示 "关闭 tab" 按钮,不持久化到 state JSON。 */
|
||||||
brainstormTabOpen?: boolean
|
brainstormTabOpen?: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user