@@ -132,6 +132,7 @@ var MAX_LISTED_GOALS = 50;
132132var CHECKPOINT_CHAR_LIMIT = 280 ;
133133var DEFAULT_NO_PROGRESS_TOKEN_THRESHOLD = 50 ;
134134var DEFAULT_MAX_NO_PROGRESS_TURNS = 2 ;
135+ var MAX_AUTO_CONTINUES_STOP_REASON_PREFIX = "max auto-continues reached (" ;
135136var PLAN_MODE_STOP_REASON = "plan mode" ;
136137var PLAN_MODE_BLOCKER = "Goal execution is paused while the session is in Plan mode. Switch to Build mode and resume the goal to continue." ;
137138var NullableString = Schema . NullOr ( Schema . String ) ;
@@ -689,7 +690,7 @@ async function pauseGoalForPlanMode(sessionID) {
689690 return snapshot ( goal ) ;
690691 } ) ;
691692}
692- async function setGoalStatus ( sessionID , status , agent ) {
693+ async function setGoalStatus ( sessionID , status , agent , options ) {
693694 const agentValue = typeof agent === "string" && agent . trim ( ) ? agent . trim ( ) : null ;
694695 return mutate ( ( state ) => {
695696 const goal = state . goals [ sessionID ] ;
@@ -701,10 +702,12 @@ async function setGoalStatus(sessionID, status, agent) {
701702 return snapshot ( goal ) ;
702703 if ( status === "paused" && goal . status !== "active" )
703704 return snapshot ( goal ) ;
705+ const resumesAutoTurnLimit = options ?. resetAutoTurnLimit === true && status === "active" && goal . status === "usageLimited" && goal . stopReason ?. startsWith ( MAX_AUTO_CONTINUES_STOP_REASON_PREFIX ) === true ;
704706 accountWallClock ( goal ) ;
705707 goal . status = status ;
706708 goal . updatedAt = nowSeconds ( ) ;
707709 goal . lastAccountedAt = status === "active" ? goal . updatedAt : null ;
710+ goal . autoTurns = resumesAutoTurnLimit ? 0 : goal . autoTurns ;
708711 goal . continuationFailures = status === "active" ? 0 : goal . continuationFailures ;
709712 goal . pendingAttempt = status === "active" ? null : goal . pendingAttempt ;
710713 goal . noProgressTurns = status === "active" ? 0 : goal . noProgressTurns ;
@@ -1053,7 +1056,7 @@ function maybeStopForUsageLimit(goal, defaultMaxAutoTurns, now = nowSeconds()) {
10531056 if ( effectiveMaxAutoTurns > 0 && goal . autoTurns >= effectiveMaxAutoTurns ) {
10541057 goal . status = "usageLimited" ;
10551058 goal . lastAccountedAt = null ;
1056- goal . stopReason = `max auto-continues reached ( ${ effectiveMaxAutoTurns } )` ;
1059+ goal . stopReason = `${ MAX_AUTO_CONTINUES_STOP_REASON_PREFIX } ${ effectiveMaxAutoTurns } )` ;
10571060 goal . lastStatus = `${ goal . stopReason } ; wrap-up required.` ;
10581061 pushHistory ( goal , "limited" , goal . lastStatus ) ;
10591062 return true ;
@@ -1617,7 +1620,7 @@ ${budgetLines(goal, locale)}
16171620\u72B6\u6001\uFF1A${ presentGoalStatus ( goal . status , locale ) }
16181621\u505C\u6B62\u539F\u56E0\uFF1A${ presentGoalStopReason ( goal . stopReason ?? "goal limit reached" , locale ) }
16191622
1620- \u4E0D\u8981\u4E3A\u6B64\u76EE\u6807\u5F00\u59CB\u65B0\u7684\u5B9E\u8D28\u6027\u5DE5\u4F5C\u3002\u5C3D\u5FEB\u7ED3\u675F\u672C\u8F6E\uFF1A\u4F7F\u7528\u7B80\u4F53\u4E2D\u6587\u603B\u7ED3\u6709\u6548\u8FDB\u5C55\uFF0C\u6307\u51FA\u5269\u4F59\u5DE5\u4F5C\u6216\u963B\u585E\u9879\uFF0C\u5E76\u7ED9\u7528\u6237\u4E00\u4E2A\u6E05\u6670\u7684\u4E0B\u4E00\u6B65\u3002\u9664\u975E\u76EE\u6807\u786E\u5B9E\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u5426\u5219\u4E0D\u8981\u8C03\u7528 update_goal\u3002` ;
1623+ \u4E0D\u8981\u4E3A\u6B64\u76EE\u6807\u5F00\u59CB\u65B0\u7684\u5B9E\u8D28\u6027\u5DE5\u4F5C\u3002\u4E0D\u8981\u8C03\u7528 update_goal_status \u6765\u7EE7\u7EED\u76EE\u6807\uFF1B\u53EA\u6709\u7528\u6237\u660E\u786E\u53D1\u51FA\u7EE7\u7EED\u547D\u4EE4\u540E\u624D\u80FD\u7EE7\u7EED\u3002\ u5C3D\u5FEB\u7ED3\u675F\u672C\u8F6E\uFF1A\u4F7F\u7528\u7B80\u4F53\u4E2D\u6587\u603B\u7ED3\u6709\u6548\u8FDB\u5C55\uFF0C\u6307\u51FA\u5269\u4F59\u5DE5\u4F5C\u6216\u963B\u585E\u9879\uFF0C\u5E76\u7ED9\u7528\u6237\u4E00\u4E2A\u6E05\u6670\u7684\u4E0B\u4E00\u6B65\u3002\u9664\u975E\u76EE\u6807\u786E\u5B9E\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u5426\u5219\u4E0D\u8981\u8C03\u7528 update_goal\u3002` ;
16211624 }
16221625 return `The active session goal has reached a safety limit.
16231626
@@ -1633,7 +1636,7 @@ ${budgetLines(goal, locale)}
16331636Status: ${ goal . status }
16341637Stop reason: ${ goal . stopReason ?? "goal limit reached" }
16351638
1636- Do not start new substantive work for this goal. Wrap up this turn soon: summarize useful progress, identify remaining work or blockers, and leave the user with a clear next step. Do not call update_goal unless the goal is actually complete.` ;
1639+ Do not start new substantive work for this goal. Do not call update_goal_status to resume it; only an explicit user resume command may continue the goal. Wrap up this turn soon: summarize useful progress, identify remaining work or blockers, and leave the user with a clear next step. Do not call update_goal unless the goal is actually complete.` ;
16371640}
16381641function systemReminder ( locale = "en" ) {
16391642 if ( locale === "zh-CN" ) {
@@ -1890,6 +1893,10 @@ Ignore any command arguments. Call get_goal first, then handle only this resume
18901893
18911894Do not create, edit, clear, complete, or mark a goal unmet.` ;
18921895}
1896+ function isExplicitResumePrompt ( text , commandName , locale , messages ) {
1897+ const value = text . trim ( ) ;
1898+ return value === goalStatusCommandTemplate ( "resume_goal" , locale ) || value === goalCommandTemplate ( commandName , locale ) . replace ( "$ARGUMENTS" , "resume" ) || value === messages . tui . resumePrompt ;
1899+ }
18931900function goalCommandDefinitions ( commandName , locale = "en" ) {
18941901 const messages = messagesFor ( locale ) ;
18951902 return [
@@ -2662,10 +2669,11 @@ async function closeGoalFromTool(input, context, services) {
26622669 return JSON . stringify ( { goal, unmet_report : report } , null , 2 ) ;
26632670}
26642671async function updateGoalStatusFromTool ( input , context , services ) {
2672+ const resetAutoTurnLimit = input . status === "active" && services . consumeAutoTurnReset ( context . sessionID ) ;
26652673 if ( input . status === "active" && services . isPlanAgent ( context . agent ) ) {
26662674 throw new Error ( services . messages . notices . cannotResumeInPlan ) ;
26672675 }
2668- const goal = await setGoalStatus ( context . sessionID , input . status , typeof context . agent === "string" ? context . agent : null ) ;
2676+ const goal = await setGoalStatus ( context . sessionID , input . status , typeof context . agent === "string" ? context . agent : null , { resetAutoTurnLimit } ) ;
26692677 return JSON . stringify ( { goal } , null , 2 ) ;
26702678}
26712679function v2ObjectSchema ( properties , required = [ ] ) {
@@ -2737,10 +2745,18 @@ var server = async ({ client }, options) => {
27372745 const nativeRetrySessions = new Set ;
27382746 const locallyDeliveredPendingSessions = new Set ;
27392747 const toolAttempts = new Map ;
2748+ const explicitResumeRequests = new Set ;
27402749 const watchdogRescuedSessions = new Set ;
27412750 const planAgents = restrictedAgentSet ( options ) ;
27422751 const isPlanAgent = ( agent ) => typeof agent === "string" && planAgents . has ( agent . trim ( ) . toLowerCase ( ) ) ;
2743- const goalServices = { options : options ?? { } , locale, messages, isPlanAgent, maxObjectiveChars : objectiveChars } ;
2752+ const goalServices = {
2753+ options : options ?? { } ,
2754+ locale,
2755+ messages,
2756+ isPlanAgent,
2757+ maxObjectiveChars : objectiveChars ,
2758+ consumeAutoTurnReset : ( sessionID ) => explicitResumeRequests . delete ( sessionID )
2759+ } ;
27442760 const stopStateRecoveryReporting = onStateRecovery ( statePath ( ) , async ( { stateFile, quarantineFile, outcome, error } ) => {
27452761 await client . app ?. log ?. ( {
27462762 body : {
@@ -3014,6 +3030,7 @@ var server = async ({ client }, options) => {
30143030 locallyDeliveredPendingSessions . clear ( ) ;
30153031 nativeRetrySessions . clear ( ) ;
30163032 toolAttempts . clear ( ) ;
3033+ explicitResumeRequests . clear ( ) ;
30173034 } ,
30183035 async config ( config ) {
30193036 if ( ! registerCommand )
@@ -3116,14 +3133,19 @@ var server = async ({ client }, options) => {
31163133 } ,
31173134 async "command.execute.before" ( input , output ) {
31183135 if ( input . command === commandName ) {
3119- escapeGoalCommandArguments ( output , goalCommandTemplate ( commandName , locale ) , input . arguments ) ;
3136+ const sanitized = escapeGoalCommandArguments ( output , goalCommandTemplate ( commandName , locale ) , input . arguments ) ;
3137+ if ( sanitized && input . arguments . trim ( ) . toLowerCase ( ) === "resume" ) {
3138+ explicitResumeRequests . add ( input . sessionID ) ;
3139+ }
31203140 return ;
31213141 }
31223142 if ( input . command !== "pause_goal" && input . command !== "resume_goal" )
31233143 return ;
31243144 const template = goalStatusCommandTemplate ( input . command , locale ) ;
31253145 if ( ! sanitizeGoalStatusCommandParts ( output , template ) )
31263146 return ;
3147+ if ( input . command === "resume_goal" )
3148+ explicitResumeRequests . add ( input . sessionID ) ;
31273149 if ( input . command !== "pause_goal" )
31283150 return ;
31293151 const goal = await getGoal ( input . sessionID ) ;
@@ -3164,7 +3186,13 @@ var server = async ({ client }, options) => {
31643186 async "chat.message" ( input , output ) {
31653187 const sessionID = typeof input ?. sessionID === "string" ? input . sessionID : output . message ?. sessionID ;
31663188 const agent = typeof input ?. agent === "string" && input . agent . trim ( ) ? input . agent : output . message ?. agent ;
3167- if ( typeof sessionID !== "string" || typeof agent !== "string" || ! agent . trim ( ) )
3189+ if ( typeof sessionID !== "string" )
3190+ return ;
3191+ explicitResumeRequests . delete ( sessionID ) ;
3192+ if ( output . parts ?. some ( ( part ) => isExplicitResumePrompt ( textFromPart ( part ) , commandName , locale , messages ) ) ) {
3193+ explicitResumeRequests . add ( sessionID ) ;
3194+ }
3195+ if ( typeof agent !== "string" || ! agent . trim ( ) )
31683196 return ;
31693197 await recordPromptAgent ( sessionID , agent ) ;
31703198 } ,
@@ -3215,6 +3243,7 @@ var server = async ({ client }, options) => {
32153243 if ( status . type === "busy" )
32163244 await markPendingContinuationStarted ( sessionID ) ;
32173245 if ( status . type === "idle" ) {
3246+ explicitResumeRequests . delete ( sessionID ) ;
32183247 busySessions . delete ( sessionID ) ;
32193248 nativeRetrySessions . delete ( sessionID ) ;
32203249 clearTurnWatchdog ( sessionID ) ;
@@ -3229,13 +3258,15 @@ var server = async ({ client }, options) => {
32293258 }
32303259 }
32313260 if ( sessionID && eventType === "session.idle" ) {
3261+ explicitResumeRequests . delete ( sessionID ) ;
32323262 busySessions . delete ( sessionID ) ;
32333263 nativeRetrySessions . delete ( sessionID ) ;
32343264 clearTurnWatchdog ( sessionID ) ;
32353265 watchdogRescuedSessions . delete ( sessionID ) ;
32363266 taskTracker . observeSessionStatus ( sessionID , "idle" ) ;
32373267 }
32383268 if ( sessionID && eventType === "session.error" ) {
3269+ explicitResumeRequests . delete ( sessionID ) ;
32393270 const inNativeRetry = nativeRetrySessions . has ( sessionID ) ;
32403271 busySessions . delete ( sessionID ) ;
32413272 clearTurnWatchdog ( sessionID ) ;
@@ -3265,6 +3296,7 @@ var server = async ({ client }, options) => {
32653296 }
32663297 }
32673298 if ( sessionID && eventType === "session.deleted" ) {
3299+ explicitResumeRequests . delete ( sessionID ) ;
32683300 busySessions . delete ( sessionID ) ;
32693301 clearTurnWatchdog ( sessionID ) ;
32703302 watchdogRescuedSessions . delete ( sessionID ) ;
@@ -3323,6 +3355,7 @@ async function setupV2(context) {
33233355 const locallyDeliveredPendingSessions = new Set ;
33243356 const watchdogRescuedSessions = new Set ;
33253357 const toolAttempts = new Map ;
3358+ const explicitResumeRequests = new Set ;
33263359 const planAgents = restrictedAgentSet ( options ) ;
33273360 const isPlanAgent = ( agent ) => typeof agent === "string" && planAgents . has ( agent . trim ( ) . toLowerCase ( ) ) ;
33283361 const activeContinuationsV2 = new Set ;
@@ -3336,6 +3369,7 @@ async function setupV2(context) {
33363369 messages,
33373370 maxObjectiveChars : objectiveChars ,
33383371 isPlanAgent,
3372+ consumeAutoTurnReset : ( sessionID ) => explicitResumeRequests . delete ( sessionID ) ,
33393373 initializeUsage : async ( sessionID ) => {
33403374 try {
33413375 await accountUsage ( sessionID , stepTokenSums . get ( sessionID ) ?? 0 , { cumulative : true , source : "v2.steps" } ) ;
@@ -3706,6 +3740,7 @@ async function setupV2(context) {
37063740 await markPendingContinuationStarted ( sessionID ) ;
37073741 }
37083742 if ( status . type === "idle" ) {
3743+ explicitResumeRequests . delete ( sessionID ) ;
37093744 busySessions . delete ( sessionID ) ;
37103745 nativeRetrySessions . delete ( sessionID ) ;
37113746 clearTurnWatchdog ( sessionID ) ;
@@ -3728,6 +3763,7 @@ async function setupV2(context) {
37283763 case "session.execution.succeeded" :
37293764 case "session.idle" : {
37303765 if ( sessionID ) {
3766+ explicitResumeRequests . delete ( sessionID ) ;
37313767 busySessions . delete ( sessionID ) ;
37323768 nativeRetrySessions . delete ( sessionID ) ;
37333769 clearTurnWatchdog ( sessionID ) ;
@@ -3744,6 +3780,7 @@ async function setupV2(context) {
37443780 case "session.execution.interrupted" : {
37453781 if ( ! sessionID )
37463782 return ;
3783+ explicitResumeRequests . delete ( sessionID ) ;
37473784 stoppedExecutions . add ( sessionID ) ;
37483785 busySessions . delete ( sessionID ) ;
37493786 nativeRetrySessions . delete ( sessionID ) ;
@@ -3757,6 +3794,7 @@ async function setupV2(context) {
37573794 case "session.execution.failed" : {
37583795 if ( ! sessionID )
37593796 return ;
3797+ explicitResumeRequests . delete ( sessionID ) ;
37603798 nativeRetrySessions . delete ( sessionID ) ;
37613799 busySessions . delete ( sessionID ) ;
37623800 clearTurnWatchdog ( sessionID ) ;
@@ -3791,6 +3829,7 @@ async function setupV2(context) {
37913829 case "session.deleted" : {
37923830 if ( ! sessionID )
37933831 return ;
3832+ explicitResumeRequests . delete ( sessionID ) ;
37943833 stoppedExecutions . delete ( sessionID ) ;
37953834 sessionOwnership . delete ( sessionID ) ;
37963835 busySessions . delete ( sessionID ) ;
@@ -3956,6 +3995,9 @@ async function setupV2(context) {
39563995 cancelScheduledContinuation ( input . sessionID ) ;
39573996 clearTurnWatchdog ( input . sessionID ) ;
39583997 }
3998+ if ( command . action === "resume" || command . action === "goal" && input . prompt . text . trim ( ) . toLowerCase ( ) === "resume" ) {
3999+ explicitResumeRequests . add ( input . sessionID ) ;
4000+ }
39594001 let forwardedPrompt = { } ;
39604002 if ( command . action === "goal" ) {
39614003 const stripMention = ( { mention : _mention , ...attachment } ) => attachment ;
@@ -3977,25 +4019,31 @@ async function setupV2(context) {
39774019 } ) ;
39784020 }
39794021 } ) ) ;
4022+ }
4023+ if ( registerCommand ) {
39804024 registrations . push ( await context . session . hook ( "prompt" , async ( input ) => {
39814025 if ( typeof input . sessionID === "string" )
39824026 markSessionOwnership ( input . sessionID , true ) ;
4027+ explicitResumeRequests . delete ( input . sessionID ) ;
39834028 const pauseTemplate = goalStatusCommandTemplate ( "pause_goal" , locale ) ;
39844029 const resumeTemplate = goalStatusCommandTemplate ( "resume_goal" , locale ) ;
39854030 const template = input . prompt . text . startsWith ( pauseTemplate ) ? pauseTemplate : input . prompt . text . startsWith ( resumeTemplate ) ? resumeTemplate : null ;
3986- if ( ! template )
3987- return ;
3988- input . prompt . text = template ;
3989- delete input . prompt . files ;
3990- delete input . prompt . agents ;
3991- delete input . prompt . skills ;
3992- if ( template !== pauseTemplate )
3993- return ;
3994- const goal = await getGoal ( input . sessionID ) ;
3995- if ( goal ?. status === "active" )
3996- await setGoalStatus ( input . sessionID , "paused" ) ;
3997- cancelScheduledContinuation ( input . sessionID ) ;
3998- clearTurnWatchdog ( input . sessionID ) ;
4031+ if ( template ) {
4032+ input . prompt . text = template ;
4033+ delete input . prompt . files ;
4034+ delete input . prompt . agents ;
4035+ delete input . prompt . skills ;
4036+ if ( template === pauseTemplate ) {
4037+ const goal = await getGoal ( input . sessionID ) ;
4038+ if ( goal ?. status === "active" )
4039+ await setGoalStatus ( input . sessionID , "paused" ) ;
4040+ cancelScheduledContinuation ( input . sessionID ) ;
4041+ clearTurnWatchdog ( input . sessionID ) ;
4042+ }
4043+ }
4044+ if ( isExplicitResumePrompt ( input . prompt . text , commandName , locale , messages ) ) {
4045+ explicitResumeRequests . add ( input . sessionID ) ;
4046+ }
39994047 } ) ) ;
40004048 }
40014049 registrations . push ( await context . tool . transform ( ( draft ) => {
@@ -4114,6 +4162,7 @@ async function setupV2(context) {
41144162 locallyDeliveredPendingSessions . clear ( ) ;
41154163 watchdogRescuedSessions . clear ( ) ;
41164164 toolAttempts . clear ( ) ;
4165+ explicitResumeRequests . clear ( ) ;
41174166 for ( const registration of registrations )
41184167 await registration . dispose ( ) ;
41194168 const termination = Promise . allSettled ( [ consumer , eventIterator ?. return ?. ( ) ] ) ;
0 commit comments