feat: /goal命令能力支持,参考codex实现 (#1261)

* feat: /goal命令能力支持,参考codex实现

* fix: 修复promp和提示词不一致的问题

* fix: 修复 goal 功能多项 AI 审查问题

- prompt 中 update 行为描述与运行时不一致(no-op → error)
- src/commands/goal/ 使用相对路径导入,改为 src/* 别名
- /goal 命令标记 bridgeSafe 但含交互式对话框,改为 false
- useGoalContinuation 中 origin 使用 as unknown as string 强转,改为直接传字符串
- ResumeConversation 路径缺少 goal hydration,补齐恢复逻辑
- onCancel 在非查询状态下误暂停 goal,加 queryGuard 守卫
- resumeGoal 允许从终态恢复,收紧为仅允许 paused 状态
- buildGoalContextBlock 生成畸形 XML 属性,改为合法 budget 属性

* fix: 修复剩余AI审查的问题

* fix: 防止goal状态丢失

* fix: 修复Biome规范错误问题

* fix: 修复部分情况下goal无法启动的问题

* fix: 增加断网后状态默认设置为PAUSE机制、完成暂停-恢复状态切换,且正常进行前端渲染。设置达到max turn后处理逻辑。

* fix: 修复终端异常断开情况,resume续跑;修复用户消息排队信息被goal输出信息覆盖的问题。

* fix: apply biome formatting to pass CI lint check

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: skip slash command echo in setUserInputOnProcessing to prevent UI flash

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: moyu <moyu@kingsoft.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
moy16
2026-06-14 10:44:10 +08:00
committed by GitHub
parent 5bfe6fa590
commit 3e3e1de81b
28 changed files with 2248 additions and 30 deletions

View File

@@ -50,6 +50,7 @@ export type LogOption = {
mode?: 'coordinator' | 'normal' // Session mode for coordinator/normal detection
worktreeSession?: PersistedWorktreeSession | null // Worktree state at session end (null = exited, undefined = never entered)
contentReplacements?: ContentReplacementRecord[] // Replacement decisions for resume reconstruction
goal?: GoalState // Active goal state at session end (for resume)
}
export type SummaryMessage = {
@@ -140,6 +141,77 @@ export type ModeEntry = {
mode: 'coordinator' | 'normal'
}
/**
* Lifecycle states for a persistent thread goal.
* - active: agent should auto-continue toward the objective
* - paused: user temporarily halted progress
* - blocked: model reported the same blocker for >=3 consecutive turns
* - budget_limited: tokensUsed >= tokenBudget (auto-transition)
* - usage_limited: provider rate/usage limit triggered (auto-transition)
* - max_turns: auto-continuation reached MAX_GOAL_TURNS safety cap
* - complete: model audit confirmed objective achieved
*/
export type GoalStatus =
| 'active'
| 'paused'
| 'blocked'
| 'budget_limited'
| 'usage_limited'
| 'max_turns'
| 'complete'
/**
* Per-session goal state. Persisted to the JSONL transcript as a `goal`
* entry on every mutation; last-wins on read.
*
* Timing fields handle pause correctly: `getActiveElapsedMs(state)`
* = accumulatedActiveMs + (now - startTime if active, else 0).
*
* `turnsExecuted` is a defensive upper bound for the auto-continuation
* loop so a runaway goal cannot spin indefinitely.
*
* `blockedAttempts` + `lastBlockReason` implement CODEX's "blocked
* only after 3 consecutive same-reason attempts" audit rule.
*/
export type GoalState = {
objective: string
status: GoalStatus
tokenBudget: number | null
tokensUsed: number
startTime: number
pausedAt: number | null
accumulatedActiveMs: number
blockedAttempts: number
lastBlockReason: string | null
createdAt: number
updatedAt: number
turnsExecuted: number
}
/**
* JSONL entry representing a goal-state checkpoint. Written on every
* mutation (set / pause / resume / complete / token update). Readers
* use the latest entry by sessionId as the authoritative state.
*/
export type GoalMetadataEntry = {
type: 'goal'
sessionId: UUID
state: GoalState
timestamp: string
}
/**
* JSONL entry signalling the user explicitly cleared the goal.
* Distinct from `complete` (which preserves the achievement). Readers
* encountering this entry after a `goal` entry should treat the goal
* as absent.
*/
export type GoalClearedEntry = {
type: 'goal-cleared'
sessionId: UUID
timestamp: string
}
/**
* Worktree session state persisted to the transcript for resume.
* Subset of WorktreeSession from utils/worktree.ts — excludes ephemeral
@@ -315,6 +387,8 @@ export type Entry =
| ContentReplacementEntry
| ContextCollapseCommitEntry
| ContextCollapseSnapshotEntry
| GoalMetadataEntry
| GoalClearedEntry
export function sortLogs(logs: LogOption[]): LogOption[] {
return logs.sort((a, b) => {