✨ feat(vscode): webhook 副作用按 assignee 门禁,适配多人协作
This commit is contained in:
@@ -7,20 +7,22 @@ import { promises as fsp } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { window, workspace } from 'vscode'
|
||||
import { loginForToken } from '../../auth/identity'
|
||||
import { getToken } from '../../auth/secrets'
|
||||
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 { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher'
|
||||
import { detectRepo } from '../../git/remote'
|
||||
import { createWorktree, resolveWorktreePath } from '../../git/worktree'
|
||||
import { updateIssueAssignees } from '../../gitea/api'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { getSettings } from '../../settings/store'
|
||||
import { webhookCoordinator } from '../../webhook/coordinator'
|
||||
import { resolveProfilePath } from '../../cc/profiles'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { decideAutoReviewPath } from '../autoReviewDecision'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
|
||||
export async function handleResumeSession(panel: KanbanWebviewPanel, sessionId: string, profilePath?: string, relCwd?: string, issueNumber?: number): Promise<void> {
|
||||
// kind 跟着 sessionRole 提前判定(原来散落在方法中部,提到入口是为了构造
|
||||
@@ -647,9 +649,12 @@ function deriveSlug(planFile: string): string {
|
||||
*/
|
||||
function resolveWorktreeDir(template: string, workspaceRoot: string, slug: string): string {
|
||||
let p = template
|
||||
.split('$project_name').join(path.basename(workspaceRoot))
|
||||
.split('$project_root').join(workspaceRoot)
|
||||
.split('$feature_name').join(slug)
|
||||
.split('$project_name')
|
||||
.join(path.basename(workspaceRoot))
|
||||
.split('$project_root')
|
||||
.join(workspaceRoot)
|
||||
.split('$feature_name')
|
||||
.join(slug)
|
||||
if (p.startsWith('~')) {
|
||||
p = path.join(os.homedir(), p.slice(1))
|
||||
}
|
||||
@@ -832,6 +837,39 @@ export async function handleImplement(
|
||||
return
|
||||
}
|
||||
|
||||
// 实施即认领:assignee 是 webhook 副作用门禁的路由依据(谁实施,谁的
|
||||
// 机器负责审查/写状态)。覆盖式单人指派,重新实施会转移责任。失败不阻塞
|
||||
// 实施——门禁侧会退回本地终端兜底。
|
||||
void (async () => {
|
||||
try {
|
||||
const me = await loginForToken(remote.host, token)
|
||||
if (!me)
|
||||
return
|
||||
await updateIssueAssignees({
|
||||
host: remote.host,
|
||||
token,
|
||||
owner: remote.owner,
|
||||
repo: remote.repo,
|
||||
index: issueNumber,
|
||||
assignees: [me],
|
||||
})
|
||||
logger.add({
|
||||
level: 'info',
|
||||
source: 'implement',
|
||||
message: `#${issueNumber} assignee → ${me}`,
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'implement',
|
||||
message: `设置 #${issueNumber} assignee 失败(不阻塞实施)`,
|
||||
details: message,
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
// Run the post-create lifecycle hook. Fire-and-await so subsequent
|
||||
// steps (mkdir projects dir, terminal spawn, cc launch) see whatever
|
||||
// setup the user script performed (e.g. .env copy). Failures are
|
||||
@@ -1223,7 +1261,7 @@ export async function handleStartTestSession(panel: KanbanWebviewPanel, issueNum
|
||||
|
||||
// worktree 里:代码就在当前工作目录,不用 tea 拉取,直接测;
|
||||
// 主 worktree:PR 已合并,先用 tea 熟悉改动再测。
|
||||
const prompt = "/pr-acceptance-testing"
|
||||
const prompt = '/pr-acceptance-testing'
|
||||
if (prompt.includes('\'')) {
|
||||
void window.showErrorMessage('启动测试失败:prompt 含单引号,拒绝执行')
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as fs from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { commands, env, Uri, workspace } from 'vscode'
|
||||
import { clearIdentityCache, initIdentity } from '../../auth/identity'
|
||||
import { getToken, getYouTrackToken, setToken, setYouTrackToken } from '../../auth/secrets'
|
||||
import { listClaudeProfiles, setProfilesDirOverride } from '../../cc/profiles'
|
||||
import { detectRepo } from '../../git/remote'
|
||||
@@ -142,8 +143,12 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
||||
// 保存后立刻让 listClaudeProfiles / getDefaultProfilePath 吃到新目录。
|
||||
setProfilesDirOverride(trimmedProfilesDirectory || undefined)
|
||||
void handleProfilesList(panel)
|
||||
if (!keepExisting)
|
||||
if (!keepExisting) {
|
||||
await setToken(panel.context, trimmedHost, trimmedToken)
|
||||
// 新 token 可能换了人,身份缓存和状态栏都要跟着重解析。
|
||||
clearIdentityCache(trimmedHost)
|
||||
void initIdentity(panel.context)
|
||||
}
|
||||
// YouTrack token: stored under the base URL's host. Empty input keeps the
|
||||
// existing token (placeholder semantics), matching the gitea flow above.
|
||||
if (trimmedYtBase && trimmedYtToken)
|
||||
|
||||
@@ -71,6 +71,22 @@ export function injectIntoImplTerminal(panel: KanbanWebviewPanel, issueNumber: n
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 本机是否开着该工单的存活终端(exitStatus undefined)——webhook 门禁对
|
||||
* 无 assignee 存量工单的兜底证据。不依赖 panel:看板关闭时实施终端
|
||||
* 仍活着,归属判定不能因此失明。前缀匹配策略与 injectIntoImplTerminal
|
||||
* 一致(shell OSC 会在名字后追加分支后缀)。
|
||||
*/
|
||||
export function hasLiveIssueSessionTerminal(
|
||||
issueNumber: number,
|
||||
kinds: readonly string[] = ['实施', '测试'],
|
||||
): boolean {
|
||||
const prefixes = kinds.map(k => `issue-${issueNumber}-${k}`)
|
||||
return window.terminals.some(t =>
|
||||
t.exitStatus === undefined && prefixes.some(p => t.name.startsWith(p)),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan `vscode.window.terminals` for an existing terminal whose name
|
||||
* matches `expectedName`. Matches exact, or `startsWith(expectedName + ' ')`
|
||||
|
||||
Reference in New Issue
Block a user