@@ -25,15 +25,40 @@ import { ALL_MODEL_CONFIGS } from '../utils/model/configs.js';
2525import { updateTaskState } from '../utils/task/framework.js' ;
2626import { archiveRemoteSession , teleportToRemote } from '../utils/teleport.js' ;
2727import { pollForApprovedExitPlanMode , UltraplanPollError } from '../utils/ultraplan/ccrSession.js' ;
28+ import {
29+ getPromptText ,
30+ getDialogConfig ,
31+ getPromptIdentifier ,
32+ type PromptIdentifier
33+ } from '../utils/ultraplan/prompt.js' ;
34+ import { registerCleanup } from '../utils/cleanupRegistry.js' ;
35+
2836
2937// TODO(prod-hardening): OAuth token may go stale over the 30min poll;
3038// consider refresh.
3139
32- // Multi-agent exploration is slow; 30min timeout.
40+ /**
41+ * Multi-agent exploration is slow; 30min timeout.
42+ *
43+ * @deprecated use getUltraplanTimeoutMs()
44+ */
3345const ULTRAPLAN_TIMEOUT_MS = 30 * 60 * 1000 ;
3446
3547export const CCR_TERMS_URL = 'https://code.claude.com/docs/en/claude-code-on-the-web' ;
3648
49+ export function getUltraplanTimeoutMs ( ) : number {
50+ return getFeatureValue_CACHED_MAY_BE_STALE ( 'tengu_ultraplan_timeout_seconds' , 1800 ) * 1000
51+ }
52+
53+ /**
54+ * 是否启用 ultraplan, 默认启用
55+ *
56+ * @returns
57+ */
58+ export function isUltraplanEnabled ( ) : boolean {
59+ return getFeatureValue_CACHED_MAY_BE_STALE < { enabled : boolean } | null > ( 'tengu_ultraplan_config' , { enabled : true } ) ?. enabled === true
60+ }
61+
3762// CCR runs against the first-party API — use the canonical ID, not the
3863// provider-specific string getModelStrings() would return (which may be a
3964// Bedrock ARN or Vertex ID on the local CLI). Read at call time, not module
@@ -62,6 +87,7 @@ const DEFAULT_INSTRUCTIONS: string = (typeof _rawPrompt === 'string' ? _rawPromp
6287// so the override path is DCE'd from external builds).
6388// Shell-set env only, so top-level process.env read is fine
6489// — settings.env never injects this.
90+ // @deprecated use buildUltraplanPrompt()
6591/* eslint-disable custom-rules/no-process-env-top-level, custom-rules/no-sync-fs -- ant-only dev override; eager top-level read is the point (crash at startup, not silently inside the slash-command try/catch) */
6692const ULTRAPLAN_INSTRUCTIONS : string =
6793 process . env . USER_TYPE === 'ant' && process . env . ULTRAPLAN_PROMPT_FILE
@@ -73,12 +99,14 @@ const ULTRAPLAN_INSTRUCTIONS: string =
7399 * Assemble the initial CCR user message. seedPlan and blurb stay outside the
74100 * system-reminder so the browser renders them; scaffolding is hidden.
75101 */
76- export function buildUltraplanPrompt ( blurb : string , seedPlan ?: string ) : string {
102+ export function buildUltraplanPrompt ( blurb : string , seedPlan ?: string , promptId ?: PromptIdentifier ) : string {
77103 const parts : string [ ] = [ ] ;
78104 if ( seedPlan ) {
79105 parts . push ( 'Here is a draft plan to refine:' , '' , seedPlan , '' ) ;
80106 }
81- parts . push ( ULTRAPLAN_INSTRUCTIONS ) ;
107+ // parts.push(ULTRAPLAN_INSTRUCTIONS)
108+ parts . push ( getPromptText ( promptId ! ) ) ;
109+
82110 if ( blurb ) {
83111 parts . push ( '' , blurb ) ;
84112 }
@@ -98,7 +126,7 @@ function startDetachedPoll(
98126 try {
99127 const { plan, rejectCount, executionTarget } = await pollForApprovedExitPlanMode (
100128 sessionId ,
101- ULTRAPLAN_TIMEOUT_MS ,
129+ getUltraplanTimeoutMs ( ) ,
102130 phase => {
103131 if ( phase === 'needs_input' ) logEvent ( 'tengu_ultraplan_awaiting_input' , { } ) ;
104132 updateTaskState < RemoteAgentTaskState > ( taskId , setAppState , t => {
@@ -258,6 +286,7 @@ export async function stopUltraplan(
258286export async function launchUltraplan ( opts : {
259287 blurb : string ;
260288 seedPlan ?: string ;
289+ promptIdentifier ?: PromptIdentifier ;
261290 getAppState : ( ) => AppState ;
262291 setAppState : ( f : ( prev : AppState ) => AppState ) => void ;
263292 signal : AbortSignal ;
@@ -272,7 +301,7 @@ export async function launchUltraplan(opts: {
272301 */
273302 onSessionReady ?: ( msg : string ) => void ;
274303} ) : Promise < string > {
275- const { blurb, seedPlan, getAppState, setAppState, signal, disconnectedBridge, onSessionReady } = opts ;
304+ const { blurb, seedPlan, promptIdentifier , getAppState, setAppState, signal, disconnectedBridge, onSessionReady } = opts ;
276305
277306 const { ultraplanSessionUrl : active , ultraplanLaunching } = getAppState ( ) ;
278307 if ( active || ultraplanLaunching ) {
@@ -292,22 +321,24 @@ export async function launchUltraplan(opts: {
292321 'Usage: /ultraplan \\<prompt\\>, or include "ultraplan" anywhere' ,
293322 'in your prompt' ,
294323 '' ,
295- 'Advanced multi-agent plan mode with our most powerful model' ,
296- '(Opus). Runs in Claude Code on the web. When the plan is ready,' ,
297- 'you can execute it in the web session or send it back here.' ,
298- 'Terminal stays free while the remote plans.' ,
299- 'Requires /login.' ,
324+ // 'Advanced multi-agent plan mode with our most powerful model',
325+ // '(Opus). Runs in Claude Code on the web. When the plan is ready,',
326+ // 'you can execute it in the web session or send it back here.',
327+ // 'Terminal stays free while the remote plans.',
328+ // 'Requires /login.',
329+ ...getDialogConfig ( ) . usageBlurb ,
300330 '' ,
301331 `Terms: ${ CCR_TERMS_URL } ` ,
302332 ] . join ( '\n' ) ;
303333 }
304334
305335 // Set synchronously before the detached flow to prevent duplicate launches
306336 // during the teleportToRemote window.
307- setAppState ( prev => ( prev . ultraplanLaunching ? prev : { ...prev , ultraplanLaunching : true } ) ) ;
337+ setAppState ( prev => prev . ultraplanLaunching ? prev : { ...prev , ultraplanLaunching : true } ) ;
308338 void launchDetached ( {
309339 blurb,
310340 seedPlan,
341+ promptIdentifier,
311342 getAppState,
312343 setAppState,
313344 signal,
@@ -319,56 +350,62 @@ export async function launchUltraplan(opts: {
319350async function launchDetached ( opts : {
320351 blurb : string ;
321352 seedPlan ?: string ;
353+ promptIdentifier ?: PromptIdentifier ;
322354 getAppState : ( ) => AppState ;
323355 setAppState : ( f : ( prev : AppState ) => AppState ) => void ;
324356 signal : AbortSignal ;
325357 onSessionReady ?: ( msg : string ) => void ;
326358} ) : Promise < void > {
327- const { blurb, seedPlan, getAppState, setAppState, signal, onSessionReady } = opts ;
359+ const { blurb, seedPlan, promptIdentifier = getPromptIdentifier ( ) , getAppState, setAppState, signal, onSessionReady } = opts ;
328360 // Hoisted so the catch block can archive the remote session if an error
329361 // occurs after teleportToRemote succeeds (avoids 30min orphan).
330362 let sessionId : string | undefined ;
331363 try {
332- const model = getUltraplanModel ( ) ;
364+ // const model = getUltraplanModel()
333365
334366 const eligibility = await checkRemoteAgentEligibility ( ) ;
335367 if ( ! eligibility . eligible ) {
336368 logEvent ( 'tengu_ultraplan_create_failed' , {
337369 reason : 'precondition' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS ,
338- precondition_errors : ( eligibility as { errors : Array < { type : string } > } ) . errors
370+ precondition_errors : eligibility . errors
339371 . map ( e => e . type )
340372 . join ( ',' ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS ,
341373 } ) ;
342- const reasons = ( eligibility as { errors : Array < { type : string } > } ) . errors . map ( formatPreconditionError ) . join ( '\n' ) ;
374+ const reasons = eligibility . errors . map ( formatPreconditionError ) . join ( '\n' ) ;
343375 enqueuePendingNotification ( {
344376 value : `ultraplan: cannot launch remote session —\n${ reasons } ` ,
345377 mode : 'task-notification' ,
346378 } ) ;
347379 return ;
348380 }
349381
350- const prompt = buildUltraplanPrompt ( blurb , seedPlan ) ;
382+ const prompt = buildUltraplanPrompt ( blurb , seedPlan , promptIdentifier ) ;
351383 let bundleFailMsg : string | undefined ;
384+ let createFailMsg : string | undefined ;
352385 const session = await teleportToRemote ( {
353386 initialMessage : prompt ,
354387 description : blurb || 'Refine local plan' ,
355- model,
388+ // model,
356389 permissionMode : 'plan' ,
357390 ultraplan : true ,
358391 signal,
359392 useDefaultEnvironment : true ,
360393 onBundleFail : msg => {
361394 bundleFailMsg = msg ;
362395 } ,
363- } ) ;
396+ onCreateFail : msg => {
397+ createFailMsg = msg ;
398+ } ,
399+ } )
364400 if ( ! session ) {
401+ let failMsg = bundleFailMsg ?? createFailMsg ;
365402 logEvent ( 'tengu_ultraplan_create_failed' , {
366403 reason : ( bundleFailMsg
367404 ? 'bundle_fail'
368- : 'teleport_null' ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS ,
405+ : createFailMsg ? 'create_api_fail' : 'teleport_null' ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS ,
369406 } ) ;
370407 enqueuePendingNotification ( {
371- value : `ultraplan: session creation failed${ bundleFailMsg ? ` — ${ bundleFailMsg } ` : '' } . See --debug for details.` ,
408+ value : `ultraplan: session creation failed${ failMsg ? ` — ${ failMsg } ` : '' } . See --debug for details.` ,
372409 mode : 'task-notification' ,
373410 } ) ;
374411 return ;
@@ -384,7 +421,8 @@ async function launchDetached(opts: {
384421 onSessionReady ?.( buildSessionReadyMessage ( url ) ) ;
385422 logEvent ( 'tengu_ultraplan_launched' , {
386423 has_seed_plan : Boolean ( seedPlan ) ,
387- model : model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS ,
424+ prompt_identifier : promptIdentifier as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
425+ // model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
388426 } ) ;
389427 // TODO(#23985): replace registerRemoteAgentTask + startDetachedPoll with
390428 // ExitPlanModeScanner inside startRemoteSessionPolling.
@@ -400,6 +438,11 @@ async function launchDetached(opts: {
400438 isUltraplan : true ,
401439 } ) ;
402440 startDetachedPoll ( taskId , session . id , url , getAppState , setAppState ) ;
441+ registerCleanup ( async ( ) => {
442+ if ( getAppState ( ) . ultraplanSessionUrl === url ) {
443+ await archiveRemoteSession ( session . id , 1500 )
444+ }
445+ } ) ;
403446 } catch ( e ) {
404447 logError ( e ) ;
405448 logEvent ( 'tengu_ultraplan_create_failed' , {
@@ -409,6 +452,13 @@ async function launchDetached(opts: {
409452 value : `ultraplan: unexpected error — ${ errorMessage ( e ) } ` ,
410453 mode : 'task-notification' ,
411454 } ) ;
455+
456+ enqueuePendingNotification ( {
457+ value : `Ultraplan hit an unexpected error during launch. Wait for the user's next instructions.` ,
458+ mode : 'task-notification' ,
459+ isMeta : true
460+ } ) ;
461+
412462 if ( sessionId ) {
413463 // Error after teleport succeeded — archive so the remote doesn't sit
414464 // running for 30min with nobody polling it.
@@ -417,11 +467,11 @@ async function launchDetached(opts: {
417467 ) ;
418468 // ultraplanSessionUrl may have been set before the throw; clear it so
419469 // the "already polling" guard doesn't block future launches.
420- setAppState ( prev => ( prev . ultraplanSessionUrl ? { ...prev , ultraplanSessionUrl : undefined } : prev ) ) ;
470+ setAppState ( prev => prev . ultraplanSessionUrl ? { ...prev , ultraplanSessionUrl : undefined } : prev ) ;
421471 }
422472 } finally {
423473 // No-op on success: the url-setting setAppState already cleared this.
424- setAppState ( prev => ( prev . ultraplanLaunching ? { ...prev , ultraplanLaunching : undefined } : prev ) ) ;
474+ setAppState ( prev => prev . ultraplanLaunching ? { ...prev , ultraplanLaunching : undefined } : prev ) ;
425475 }
426476}
427477
@@ -469,6 +519,7 @@ export default {
469519 name : 'ultraplan' ,
470520 description : `~10–30 min · Claude Code on the web drafts an advanced plan you can edit and approve. See ${ CCR_TERMS_URL } ` ,
471521 argumentHint : '<prompt>' ,
472- isEnabled : ( ) => true ,
522+ // isEnabled: () => process.env.USER_TYPE === 'ant',
523+ isEnabled : ( ) => isUltraplanEnabled ( ) ,
473524 load : ( ) => Promise . resolve ( { call } ) ,
474525} satisfies Command ;
0 commit comments