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 属性
This commit is contained in:
moyu 2026-06-08 18:04:42 +08:00
parent beb01cb455
commit c551f5ae81
8 changed files with 35 additions and 28 deletions

View File

@ -6,10 +6,10 @@ import * as React from 'react';
import { Box, Text } from '@anthropic/ink';
import type { GoalState } from '../../types/logs.js';
import { Select } from '../../components/CustomSelect/index.js';
import { PermissionDialog } from '../../components/permissions/PermissionDialog.js';
import { formatGoalElapsed, formatGoalStatusLabel } from '../../services/goal/goalState.js';
import type { GoalState } from 'src/types/logs.js';
import { Select } from 'src/components/CustomSelect/index.js';
import { PermissionDialog } from 'src/components/permissions/PermissionDialog.js';
import { formatGoalElapsed, formatGoalStatusLabel } from 'src/services/goal/goalState.js';
type Props = {
currentGoal: GoalState;

View File

@ -15,8 +15,8 @@
*/
import * as React from 'react';
import type { LocalJSXCommandContext } from '../../commands.js';
import type { LocalJSXCommandOnDone } from '../../types/command.js';
import type { LocalJSXCommandContext } from 'src/commands.js';
import type { LocalJSXCommandOnDone } from 'src/types/command.js';
import {
clearGoal,
completeGoal,
@ -26,8 +26,8 @@ import {
pauseGoal,
resumeGoal,
setGoal,
} from '../../services/goal/goalState.js';
import { persistCurrentGoal, persistGoalClear } from '../../services/goal/goalStorage.js';
} from 'src/services/goal/goalState.js';
import { persistCurrentGoal, persistGoalClear } from 'src/services/goal/goalStorage.js';
import { GoalReplaceConfirmDialog } from './GoalReplaceConfirmDialog.js';
const MAX_OBJECTIVE_CHARS = 4000;

View File

@ -1,4 +1,4 @@
import type { Command } from '../../commands.js'
import type { Command } from 'src/commands.js'
const goal = {
type: 'local-jsx',
@ -6,7 +6,7 @@ const goal = {
description:
'Set or view a persistent goal that drives auto-continuation across turns',
argumentHint: '[<objective> | status | clear | pause | resume | complete]',
bridgeSafe: true,
bridgeSafe: false,
load: () => import('./goal.js'),
} satisfies Command

View File

@ -21,22 +21,22 @@
*/
import { useEffect, useRef } from 'react'
import { logForDebugging } from '../utils/debug.js'
import { logForDebugging } from 'src/utils/debug.js'
import {
getGoal,
incrementGoalTurns,
MAX_GOAL_TURNS,
} from '../services/goal/goalState.js'
} from 'src/services/goal/goalState.js'
import { persistCurrentGoal } from 'src/services/goal/goalStorage.js'
import {
buildBudgetLimitPrompt,
buildContinuationPrompt,
} from 'src/services/goal/prompts.js'
import { enqueue } from 'src/utils/messageQueueManager.js'
function hookLog(msg: string): void {
logForDebugging(`[goal] hook: ${msg}`)
}
import { persistCurrentGoal } from '../services/goal/goalStorage.js'
import {
buildBudgetLimitPrompt,
buildContinuationPrompt,
} from '../services/goal/prompts.js'
import { enqueue } from '../utils/messageQueueManager.js'
export type UseGoalContinuationOpts = {
isLoading: boolean
@ -94,7 +94,7 @@ export function useGoalContinuation(opts: UseGoalContinuationOpts): void {
mode: 'prompt',
priority: 'later',
isMeta: true,
origin: { kind: 'goal-budget-limit' } as unknown as string,
origin: 'goal-budget-limit',
skipSlashCommands: true,
})
return
@ -127,7 +127,7 @@ export function useGoalContinuation(opts: UseGoalContinuationOpts): void {
mode: 'prompt',
priority: 'later',
isMeta: true,
origin: { kind: 'goal-continuation' } as unknown as string,
origin: 'goal-continuation',
skipSlashCommands: true,
})
}, [

View File

@ -2544,7 +2544,10 @@ export function REPL({
// Ctrl+C during an active goal turn pauses the goal so the
// continuation loop stops. The user can /goal resume to continue later.
if (feature('GOAL')) {
// Guard: only pause when a query is actually in flight. onCancel() is
// also called from the restore/edit flow (idle), and pausing then would
// incorrectly stop the next continuation.
if (feature('GOAL') && queryGuard.getSnapshot()) {
const { getGoal, pauseGoal } =
require('../services/goal/goalState.js') as typeof import('../services/goal/goalState.js');
const { persistCurrentGoal } =

View File

@ -1,4 +1,5 @@
import { feature } from 'bun:bundle';
import type { UUID } from 'crypto';
import { dirname } from 'path';
import React from 'react';
import { useTerminalSize } from 'src/hooks/useTerminalSize.js';
@ -296,6 +297,14 @@ export function ResumeConversation({
}
}
if (feature('GOAL') && log.goal && result.sessionId) {
const { hydrateGoalFromTranscript } =
require('src/services/goal/goalStorage.js') as typeof import('src/services/goal/goalStorage.js');
const goalsMap = new Map<UUID, import('src/types/logs.js').GoalState>();
goalsMap.set(result.sessionId, log.goal);
hydrateGoalFromTranscript(goalsMap, result.sessionId);
}
if (feature('CONTEXT_COLLAPSE')) {
/* eslint-disable @typescript-eslint/no-require-imports */
(

View File

@ -91,12 +91,7 @@ export function resumeGoal(sessionId?: string): GoalState | null {
const id = resolveSessionId(sessionId)
const goal = goals.get(id)
if (!goal) return null
if (
goal.status !== 'paused' &&
goal.status !== 'blocked' &&
goal.status !== 'budget_limited' &&
goal.status !== 'usage_limited'
) {
if (goal.status !== 'paused') {
return null
}
const now = Date.now()

View File

@ -140,7 +140,7 @@ export function buildGoalContextBlock(goal: GoalState): string {
const elapsedMs = getActiveElapsedMs(goal)
const budget =
goal.tokenBudget !== null
? ` | budget: ${goal.tokensUsed}/${goal.tokenBudget}`
? ` budget="${goal.tokenBudget}"`
: ''
return [