Repository navigation
feat(core): add Goal v3 worker tools #7729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
4271805
e85b026
f67e627
bbbe160
eebac98
9597bf2
110a600
a0581d2
70f977b
6ef0893
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
| */ | ||
|
|
||
| export const GOAL_STATE_VERSION = 2 as const; | ||
| export const GOAL_PROPOSAL_REASON_MAX_CHARACTERS = 8_000; | ||
| export const GOAL_PROPOSAL_REASON_MAX_BYTES = 16_000; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Keep valid inputs within the verifier request budget A valid 24,000-byte delivered-output evidence record is serialized both in — Codex GPT-5 via Qwen Code /review
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 已修复 验证证据:commit a0581d2 用 currentTurnId 标识当前轮 delivered_output,移除 evidence content 的重复序列化;24,000-byte evidence + 16,000-byte reason 的回归用例通过,Goal 回归 280/280、build/typecheck 通过。 |
||
|
|
||
| export const PAUSED_GOAL_SYSTEM_REMINDER = | ||
| '<system-reminder>\nThe Goal is paused. Do not continue its objective unless the user resumes it. Treat this message as ordinary conversation.\n</system-reminder>'; | ||
|
|
@@ -99,6 +101,19 @@ export interface GoalTerminalProposal { | |
| blockerKind?: 'authority' | 'external' | 'repeated'; | ||
| } | ||
|
|
||
| export function validateGoalProposalReason(reason: string): string | null { | ||
| if (!reason.trim()) return 'Goal proposal reason must not be empty'; | ||
| if ([...reason].length > GOAL_PROPOSAL_REASON_MAX_CHARACTERS) { | ||
| return `Goal proposal reason exceeds ${GOAL_PROPOSAL_REASON_MAX_CHARACTERS} characters`; | ||
| } | ||
| if ( | ||
| new TextEncoder().encode(reason).byteLength > GOAL_PROPOSAL_REASON_MAX_BYTES | ||
| ) { | ||
| return `Goal proposal reason exceeds ${GOAL_PROPOSAL_REASON_MAX_BYTES} UTF-8 bytes`; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| export type GoalStateCause = | ||
| | 'create' | ||
| | 'replace' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import { | |
| type GoalTerminalProposal, | ||
| type GoalTurnPermit, | ||
| type TranscriptCursor, | ||
| validateGoalProposalReason, | ||
| } from './goal-protocol.js'; | ||
| import { | ||
| elapsedActiveTime, | ||
|
|
@@ -40,6 +41,9 @@ import { | |
| type GoalRecoveryRecord, | ||
| } from './goal-persistence.js'; | ||
|
|
||
| export const GOAL_RUNTIME_DISPOSED_MESSAGE = 'Goal runtime has been disposed'; | ||
| export const STALE_GOAL_TURN_MESSAGE = 'Goal turn permit is no longer valid'; | ||
|
|
||
| export interface GoalJournal { | ||
| getTranscriptCursor(): TranscriptCursor; | ||
| recordGoalState( | ||
|
|
@@ -89,6 +93,7 @@ export interface GoalPendingProposal { | |
|
|
||
| export interface GoalRuntime { | ||
| getSnapshot(): GoalSnapshotV2; | ||
| getSnapshotForPermit(permit: GoalTurnPermit): GoalSnapshotV2; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Avoid breaking existing
— Codex GPT-5 via Qwen Code /review |
||
| subscribe( | ||
| listener: (snapshot: GoalSnapshotV2, cause?: GoalStateCause) => void, | ||
| ): () => void; | ||
|
|
@@ -162,7 +167,7 @@ export function createGoalRuntime( | |
| type VerificationAttempt = NonNullable<typeof verificationAttempt>; | ||
|
|
||
| const assertAvailable = () => { | ||
| if (disposed) throw new Error('Goal runtime has been disposed'); | ||
| if (disposed) throw new Error(GOAL_RUNTIME_DISPOSED_MESSAGE); | ||
| }; | ||
|
|
||
| const assertOperational = () => { | ||
|
|
@@ -295,6 +300,14 @@ export function createGoalRuntime( | |
| currentPermit.revision === permit.revision && | ||
| currentPermit.turnId === permit.turnId; | ||
|
|
||
| const getSnapshotForPermit = (permit: GoalTurnPermit): GoalSnapshotV2 => { | ||
| assertOperational(); | ||
| if (!isCurrentPermit(permit) || !snapshot.goal) { | ||
| throw new Error(STALE_GOAL_TURN_MESSAGE); | ||
| } | ||
| return getSnapshot(); | ||
| }; | ||
|
|
||
| const isCurrentVerificationAttempt = (attempt: VerificationAttempt) => | ||
| verificationAttempt === attempt && | ||
| snapshot.goal?.goalId === attempt.permit.goalId && | ||
|
|
@@ -531,6 +544,7 @@ export function createGoalRuntime( | |
|
|
||
| return { | ||
| getSnapshot, | ||
| getSnapshotForPermit, | ||
| subscribe( | ||
| listener: (value: GoalSnapshotV2, cause?: GoalStateCause) => void, | ||
| ): () => void { | ||
|
|
@@ -659,7 +673,7 @@ export function createGoalRuntime( | |
| getVerifierFeedback(permit: GoalTurnPermit): string | undefined { | ||
| assertOperational(); | ||
| if (!isCurrentPermit(permit)) { | ||
| throw new Error('Goal turn permit is no longer valid'); | ||
| throw new Error(STALE_GOAL_TURN_MESSAGE); | ||
| } | ||
| return currentTurnFeedback; | ||
| }, | ||
|
|
@@ -668,7 +682,7 @@ export function createGoalRuntime( | |
| async (): Promise<VerificationAttempt | undefined> => { | ||
| assertOperational(); | ||
| if (!isCurrentPermit(permit) || !snapshot.goal) { | ||
| throw new Error('Goal turn permit is no longer valid'); | ||
| throw new Error(STALE_GOAL_TURN_MESSAGE); | ||
| } | ||
| const recordUuid = randomUUID(); | ||
| const nextGoal = reduceGoalTurnFinished(snapshot.goal, { | ||
|
|
@@ -754,7 +768,7 @@ export function createGoalRuntime( | |
| async getGoalForWorker(permit: GoalTurnPermit): Promise<GoalWorkerView> { | ||
| assertOperational(); | ||
| if (!isCurrentPermit(permit) || !snapshot.goal) { | ||
| throw new Error('Goal turn permit is no longer valid'); | ||
| throw new Error(STALE_GOAL_TURN_MESSAGE); | ||
| } | ||
| const goal = structuredClone(snapshot.goal); | ||
| const verifierFeedback = currentTurnFeedback; | ||
|
|
@@ -776,7 +790,7 @@ export function createGoalRuntime( | |
| permit, | ||
| }); | ||
| if (!isCurrentPermit(permit) || !snapshot.goal) { | ||
| throw new Error('Goal turn permit is no longer valid'); | ||
| throw new Error(STALE_GOAL_TURN_MESSAGE); | ||
| } | ||
| return { | ||
| goalId: goal.goalId, | ||
|
|
@@ -793,8 +807,10 @@ export function createGoalRuntime( | |
| ): GoalProposalReceipt { | ||
| assertOperational(); | ||
| if (!isCurrentPermit(permit)) { | ||
| throw new Error('Goal turn permit is no longer valid'); | ||
| throw new Error(STALE_GOAL_TURN_MESSAGE); | ||
| } | ||
| const reasonError = validateGoalProposalReason(proposal.reason); | ||
| if (reasonError) throw new Error(reasonError); | ||
| if (currentProposal) { | ||
| return { | ||
| recorded: false, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] Cover the inclusive reason boundaries
The tests exercise short valid values and values just over these limits, but not exactly 8,000 characters or 16,000 UTF-8 bytes. An accidental
>to>=change would reject documented maximum-sized reasons while the current tests still pass. Add exact-boundary acceptance cases for both limits.— Codex GPT-5 via Qwen Code /review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
已修复
验证证据:commit a0581d2 新增 8,000 字符与 16,000 UTF-8 字节精确边界接受用例;Goal 回归 280/280 通过。