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:
parent
beb01cb455
commit
c551f5ae81
|
|
@ -6,10 +6,10 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { Box, Text } from '@anthropic/ink';
|
import { Box, Text } from '@anthropic/ink';
|
||||||
|
|
||||||
import type { GoalState } from '../../types/logs.js';
|
import type { GoalState } from 'src/types/logs.js';
|
||||||
import { Select } from '../../components/CustomSelect/index.js';
|
import { Select } from 'src/components/CustomSelect/index.js';
|
||||||
import { PermissionDialog } from '../../components/permissions/PermissionDialog.js';
|
import { PermissionDialog } from 'src/components/permissions/PermissionDialog.js';
|
||||||
import { formatGoalElapsed, formatGoalStatusLabel } from '../../services/goal/goalState.js';
|
import { formatGoalElapsed, formatGoalStatusLabel } from 'src/services/goal/goalState.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentGoal: GoalState;
|
currentGoal: GoalState;
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import type { LocalJSXCommandContext } from '../../commands.js';
|
import type { LocalJSXCommandContext } from 'src/commands.js';
|
||||||
import type { LocalJSXCommandOnDone } from '../../types/command.js';
|
import type { LocalJSXCommandOnDone } from 'src/types/command.js';
|
||||||
import {
|
import {
|
||||||
clearGoal,
|
clearGoal,
|
||||||
completeGoal,
|
completeGoal,
|
||||||
|
|
@ -26,8 +26,8 @@ import {
|
||||||
pauseGoal,
|
pauseGoal,
|
||||||
resumeGoal,
|
resumeGoal,
|
||||||
setGoal,
|
setGoal,
|
||||||
} from '../../services/goal/goalState.js';
|
} from 'src/services/goal/goalState.js';
|
||||||
import { persistCurrentGoal, persistGoalClear } from '../../services/goal/goalStorage.js';
|
import { persistCurrentGoal, persistGoalClear } from 'src/services/goal/goalStorage.js';
|
||||||
import { GoalReplaceConfirmDialog } from './GoalReplaceConfirmDialog.js';
|
import { GoalReplaceConfirmDialog } from './GoalReplaceConfirmDialog.js';
|
||||||
|
|
||||||
const MAX_OBJECTIVE_CHARS = 4000;
|
const MAX_OBJECTIVE_CHARS = 4000;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Command } from '../../commands.js'
|
import type { Command } from 'src/commands.js'
|
||||||
|
|
||||||
const goal = {
|
const goal = {
|
||||||
type: 'local-jsx',
|
type: 'local-jsx',
|
||||||
|
|
@ -6,7 +6,7 @@ const goal = {
|
||||||
description:
|
description:
|
||||||
'Set or view a persistent goal that drives auto-continuation across turns',
|
'Set or view a persistent goal that drives auto-continuation across turns',
|
||||||
argumentHint: '[<objective> | status | clear | pause | resume | complete]',
|
argumentHint: '[<objective> | status | clear | pause | resume | complete]',
|
||||||
bridgeSafe: true,
|
bridgeSafe: false,
|
||||||
load: () => import('./goal.js'),
|
load: () => import('./goal.js'),
|
||||||
} satisfies Command
|
} satisfies Command
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,22 +21,22 @@
|
||||||
*/
|
*/
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { logForDebugging } from 'src/utils/debug.js'
|
||||||
import {
|
import {
|
||||||
getGoal,
|
getGoal,
|
||||||
incrementGoalTurns,
|
incrementGoalTurns,
|
||||||
MAX_GOAL_TURNS,
|
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 {
|
function hookLog(msg: string): void {
|
||||||
logForDebugging(`[goal] hook: ${msg}`)
|
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 = {
|
export type UseGoalContinuationOpts = {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
|
|
@ -94,7 +94,7 @@ export function useGoalContinuation(opts: UseGoalContinuationOpts): void {
|
||||||
mode: 'prompt',
|
mode: 'prompt',
|
||||||
priority: 'later',
|
priority: 'later',
|
||||||
isMeta: true,
|
isMeta: true,
|
||||||
origin: { kind: 'goal-budget-limit' } as unknown as string,
|
origin: 'goal-budget-limit',
|
||||||
skipSlashCommands: true,
|
skipSlashCommands: true,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
@ -127,7 +127,7 @@ export function useGoalContinuation(opts: UseGoalContinuationOpts): void {
|
||||||
mode: 'prompt',
|
mode: 'prompt',
|
||||||
priority: 'later',
|
priority: 'later',
|
||||||
isMeta: true,
|
isMeta: true,
|
||||||
origin: { kind: 'goal-continuation' } as unknown as string,
|
origin: 'goal-continuation',
|
||||||
skipSlashCommands: true,
|
skipSlashCommands: true,
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
|
|
|
||||||
|
|
@ -2544,7 +2544,10 @@ export function REPL({
|
||||||
|
|
||||||
// Ctrl+C during an active goal turn pauses the goal so the
|
// Ctrl+C during an active goal turn pauses the goal so the
|
||||||
// continuation loop stops. The user can /goal resume to continue later.
|
// 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 } =
|
const { getGoal, pauseGoal } =
|
||||||
require('../services/goal/goalState.js') as typeof import('../services/goal/goalState.js');
|
require('../services/goal/goalState.js') as typeof import('../services/goal/goalState.js');
|
||||||
const { persistCurrentGoal } =
|
const { persistCurrentGoal } =
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { feature } from 'bun:bundle';
|
import { feature } from 'bun:bundle';
|
||||||
|
import type { UUID } from 'crypto';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTerminalSize } from 'src/hooks/useTerminalSize.js';
|
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')) {
|
if (feature('CONTEXT_COLLAPSE')) {
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -91,12 +91,7 @@ export function resumeGoal(sessionId?: string): GoalState | null {
|
||||||
const id = resolveSessionId(sessionId)
|
const id = resolveSessionId(sessionId)
|
||||||
const goal = goals.get(id)
|
const goal = goals.get(id)
|
||||||
if (!goal) return null
|
if (!goal) return null
|
||||||
if (
|
if (goal.status !== 'paused') {
|
||||||
goal.status !== 'paused' &&
|
|
||||||
goal.status !== 'blocked' &&
|
|
||||||
goal.status !== 'budget_limited' &&
|
|
||||||
goal.status !== 'usage_limited'
|
|
||||||
) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ export function buildGoalContextBlock(goal: GoalState): string {
|
||||||
const elapsedMs = getActiveElapsedMs(goal)
|
const elapsedMs = getActiveElapsedMs(goal)
|
||||||
const budget =
|
const budget =
|
||||||
goal.tokenBudget !== null
|
goal.tokenBudget !== null
|
||||||
? ` | budget: ${goal.tokensUsed}/${goal.tokenBudget}`
|
? ` budget="${goal.tokenBudget}"`
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user