@@ -55,11 +55,13 @@ import type {
5555 ChannelDeliveryInfo ,
5656 ClientMcpMessageSender ,
5757 CreateSubSessionHandler ,
58+ CurrentSessionScheduledTaskCreateHandler ,
5859 ExternalToolGuardHandler ,
5960 LiveScreenContextCaptureHandler ,
6061 LiveSpeakToUserHandler ,
6162 LiveTaskToolRequestHandler ,
6263} from './bridgeOptions.js' ;
64+
6365import {
6466 CHANNEL_DELIVERY_ERROR_CODES ,
6567 LIVE_TASK_TOOL_NAMES ,
@@ -94,6 +96,9 @@ import {
9496 type SessionAttachmentStore ,
9597} from './sessionAttachments.js' ;
9698
99+ const MAX_SCHEDULED_TASK_CRON_CHARS = 200 ;
100+ const MAX_SCHEDULED_TASK_PROMPT_CHARS = 100_000 ;
101+
97102/**
98103 * Validate a channel-wide active-work snapshot off the wire.
99104 *
@@ -617,6 +622,9 @@ export interface BridgeClientSessionEntry {
617622 sessionId : string ;
618623 workspaceCwd : string ;
619624 effectiveCwd : string ;
625+ parentSessionId ?: string ;
626+ sourceType ?: string ;
627+ sourceId ?: string ;
620628 events : EventBus ;
621629 artifacts : SessionArtifactStore ;
622630 attachments : SessionAttachmentStore ;
@@ -849,6 +857,7 @@ export class BridgeClient implements Client {
849857 * source-compatible.
850858 */
851859 private readonly onGoalTurnEnded ?: ( sessionId : string ) => void ,
860+ private readonly onCreateCurrentSessionScheduledTask ?: CurrentSessionScheduledTaskCreateHandler ,
852861 ) { }
853862
854863 async requestPermission (
@@ -1237,6 +1246,11 @@ export class BridgeClient implements Client {
12371246 if ( method === SERVE_CONTROL_EXT_METHODS . createSubSession ) {
12381247 return this . handleCreateSubSession ( params ) ;
12391248 }
1249+ if (
1250+ method === SERVE_CONTROL_EXT_METHODS . createCurrentSessionScheduledTask
1251+ ) {
1252+ return this . handleCreateCurrentSessionScheduledTask ( params ) ;
1253+ }
12401254 if ( method === SERVE_CONTROL_EXT_METHODS . liveCaptureScreenContext ) {
12411255 return this . handleLiveScreenContextCapture ( params ) ;
12421256 }
@@ -1839,6 +1853,106 @@ export class BridgeClient implements Client {
18391853 } ;
18401854 }
18411855
1856+ private async handleCreateCurrentSessionScheduledTask (
1857+ params : Record < string , unknown > ,
1858+ ) : Promise < Record < string , unknown > > {
1859+ if ( ! this . onCreateCurrentSessionScheduledTask ) {
1860+ throw RequestError . methodNotFound (
1861+ SERVE_CONTROL_EXT_METHODS . createCurrentSessionScheduledTask ,
1862+ ) ;
1863+ }
1864+ const callerSessionId = params [ 'callerSessionId' ] ;
1865+ const promptId = params [ 'promptId' ] ;
1866+ const cron = params [ 'cron' ] ;
1867+ const prompt = params [ 'prompt' ] ;
1868+ const recurring = params [ 'recurring' ] ;
1869+ if (
1870+ typeof callerSessionId !== 'string' ||
1871+ callerSessionId . length === 0 ||
1872+ ! this . ownsSession ( callerSessionId )
1873+ ) {
1874+ throw RequestError . invalidParams (
1875+ undefined ,
1876+ '`callerSessionId` must name a session owned by this connection' ,
1877+ ) ;
1878+ }
1879+ if ( typeof promptId !== 'string' || promptId . length === 0 ) {
1880+ throw RequestError . invalidParams (
1881+ undefined ,
1882+ '`promptId` must be a non-empty string' ,
1883+ ) ;
1884+ }
1885+ if (
1886+ typeof cron !== 'string' ||
1887+ cron . length === 0 ||
1888+ cron . length > MAX_SCHEDULED_TASK_CRON_CHARS
1889+ ) {
1890+ throw RequestError . invalidParams (
1891+ undefined ,
1892+ `\`cron\` must be a non-empty string within the ${ MAX_SCHEDULED_TASK_CRON_CHARS } -character limit` ,
1893+ ) ;
1894+ }
1895+ if (
1896+ typeof prompt !== 'string' ||
1897+ prompt . length === 0 ||
1898+ prompt . length > MAX_SCHEDULED_TASK_PROMPT_CHARS
1899+ ) {
1900+ throw RequestError . invalidParams (
1901+ undefined ,
1902+ `\`prompt\` must be non-empty and within the ${ MAX_SCHEDULED_TASK_PROMPT_CHARS } -character limit` ,
1903+ ) ;
1904+ }
1905+ if ( typeof recurring !== 'boolean' ) {
1906+ throw RequestError . invalidParams (
1907+ undefined ,
1908+ '`recurring` must be a boolean' ,
1909+ ) ;
1910+ }
1911+
1912+ const entry = this . resolveEntry ( callerSessionId ) ;
1913+ if (
1914+ ! entry ||
1915+ entry . sessionId !== callerSessionId ||
1916+ entry . promptActive !== true ||
1917+ entry . activePromptId !== promptId
1918+ ) {
1919+ throw RequestError . invalidParams (
1920+ undefined ,
1921+ 'The caller session does not own the active prompt' ,
1922+ ) ;
1923+ }
1924+ if (
1925+ entry . parentSessionId !== undefined ||
1926+ entry . sourceId !== undefined ||
1927+ ( entry . sourceType !== undefined && entry . sourceType !== 'default' )
1928+ ) {
1929+ throw RequestError . invalidParams (
1930+ undefined ,
1931+ 'The caller session source cannot own a scheduled task' ,
1932+ ) ;
1933+ }
1934+
1935+ const result = await this . onCreateCurrentSessionScheduledTask ( {
1936+ callerSessionId,
1937+ promptId,
1938+ cron,
1939+ prompt,
1940+ recurring,
1941+ } ) ;
1942+ if (
1943+ typeof result . id !== 'string' ||
1944+ result . id . length === 0 ||
1945+ typeof result . cron !== 'string' ||
1946+ result . cron . length === 0
1947+ ) {
1948+ throw RequestError . internalError (
1949+ undefined ,
1950+ 'Scheduled-task host returned an invalid result' ,
1951+ ) ;
1952+ }
1953+ return { id : result . id , cron : result . cron } ;
1954+ }
1955+
18421956 private async handleLiveScreenContextCapture (
18431957 params : Record < string , unknown > ,
18441958 ) : Promise < Record < string , unknown > > {
0 commit comments