Repository navigation
feat(daemon): Support current-session scheduled tasks #9838
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 4 commits
1075c65
7c8a4b3
4693c7a
1180fcd
98d15a5
8291f12
2391398
8ca008d
16a5786
cf63c6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,11 +55,13 @@ import type { | |
| ChannelDeliveryInfo, | ||
| ClientMcpMessageSender, | ||
| CreateSubSessionHandler, | ||
| CurrentSessionScheduledTaskCreateHandler, | ||
| ExternalToolGuardHandler, | ||
| LiveScreenContextCaptureHandler, | ||
| LiveSpeakToUserHandler, | ||
| LiveTaskToolRequestHandler, | ||
| } from './bridgeOptions.js'; | ||
|
|
||
| import { | ||
| CHANNEL_DELIVERY_ERROR_CODES, | ||
| LIVE_TASK_TOOL_NAMES, | ||
|
|
@@ -94,6 +96,9 @@ import { | |
| type SessionAttachmentStore, | ||
| } from './sessionAttachments.js'; | ||
|
|
||
| const MAX_SCHEDULED_TASK_CRON_CHARS = 200; | ||
| const MAX_SCHEDULED_TASK_PROMPT_CHARS = 100_000; | ||
|
|
||
| /** | ||
| * Validate a channel-wide active-work snapshot off the wire. | ||
| * | ||
|
|
@@ -617,6 +622,9 @@ export interface BridgeClientSessionEntry { | |
| sessionId: string; | ||
| workspaceCwd: string; | ||
| effectiveCwd: string; | ||
| parentSessionId?: string; | ||
| sourceType?: string; | ||
| sourceId?: string; | ||
|
doudouOUC marked this conversation as resolved.
|
||
| events: EventBus; | ||
| artifacts: SessionArtifactStore; | ||
| attachments: SessionAttachmentStore; | ||
|
|
@@ -849,6 +857,7 @@ export class BridgeClient implements Client { | |
| * source-compatible. | ||
| */ | ||
| private readonly onGoalTurnEnded?: (sessionId: string) => void, | ||
| private readonly onCreateCurrentSessionScheduledTask?: CurrentSessionScheduledTaskCreateHandler, | ||
| ) {} | ||
|
|
||
| async requestPermission( | ||
|
|
@@ -1237,6 +1246,11 @@ export class BridgeClient implements Client { | |
| if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) { | ||
| return this.handleCreateSubSession(params); | ||
| } | ||
| if ( | ||
| method === SERVE_CONTROL_EXT_METHODS.createCurrentSessionScheduledTask | ||
| ) { | ||
| return this.handleCreateCurrentSessionScheduledTask(params); | ||
| } | ||
| if (method === SERVE_CONTROL_EXT_METHODS.liveCaptureScreenContext) { | ||
| return this.handleLiveScreenContextCapture(params); | ||
| } | ||
|
|
@@ -1839,6 +1853,106 @@ export class BridgeClient implements Client { | |
| }; | ||
| } | ||
|
|
||
| private async handleCreateCurrentSessionScheduledTask( | ||
| params: Record<string, unknown>, | ||
| ): Promise<Record<string, unknown>> { | ||
| if (!this.onCreateCurrentSessionScheduledTask) { | ||
| throw RequestError.methodNotFound( | ||
| SERVE_CONTROL_EXT_METHODS.createCurrentSessionScheduledTask, | ||
| ); | ||
| } | ||
| const callerSessionId = params['callerSessionId']; | ||
| const promptId = params['promptId']; | ||
| const cron = params['cron']; | ||
| const prompt = params['prompt']; | ||
| const recurring = params['recurring']; | ||
| if ( | ||
| typeof callerSessionId !== 'string' || | ||
| callerSessionId.length === 0 || | ||
| !this.ownsSession(callerSessionId) | ||
| ) { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| '`callerSessionId` must name a session owned by this connection', | ||
| ); | ||
| } | ||
| if (typeof promptId !== 'string' || promptId.length === 0) { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| '`promptId` must be a non-empty string', | ||
| ); | ||
| } | ||
| if ( | ||
| typeof cron !== 'string' || | ||
| cron.length === 0 || | ||
| cron.length > MAX_SCHEDULED_TASK_CRON_CHARS | ||
| ) { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| `\`cron\` must be a non-empty string within the ${MAX_SCHEDULED_TASK_CRON_CHARS}-character limit`, | ||
| ); | ||
| } | ||
| if ( | ||
| typeof prompt !== 'string' || | ||
| prompt.length === 0 || | ||
| prompt.length > MAX_SCHEDULED_TASK_PROMPT_CHARS | ||
| ) { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| `\`prompt\` must be non-empty and within the ${MAX_SCHEDULED_TASK_PROMPT_CHARS}-character limit`, | ||
| ); | ||
| } | ||
| if (typeof recurring !== 'boolean') { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| '`recurring` must be a boolean', | ||
| ); | ||
| } | ||
|
|
||
| const entry = this.resolveEntry(callerSessionId); | ||
| if ( | ||
| !entry || | ||
| entry.sessionId !== callerSessionId || | ||
| entry.promptActive !== true || | ||
| entry.activePromptId !== promptId | ||
| ) { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| 'The caller session does not own the active prompt', | ||
| ); | ||
| } | ||
| if ( | ||
| entry.parentSessionId !== undefined || | ||
|
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. Source-eligibility from in-memory bridge entry may be incomplete after restart (minor): The check here reads The Not a blocker given the defense-in-depth path, but worth confirming that |
||
| entry.sourceId !== undefined || | ||
| (entry.sourceType !== undefined && entry.sourceType !== 'default') | ||
| ) { | ||
| throw RequestError.invalidParams( | ||
| undefined, | ||
| 'The caller session source cannot own a scheduled task', | ||
| ); | ||
| } | ||
|
|
||
| const result = await this.onCreateCurrentSessionScheduledTask({ | ||
| callerSessionId, | ||
| promptId, | ||
| cron, | ||
| prompt, | ||
| recurring, | ||
| }); | ||
| if ( | ||
| typeof result.id !== 'string' || | ||
| result.id.length === 0 || | ||
| typeof result.cron !== 'string' || | ||
| result.cron.length === 0 | ||
| ) { | ||
| throw RequestError.internalError( | ||
| undefined, | ||
| 'Scheduled-task host returned an invalid result', | ||
| ); | ||
| } | ||
| return { id: result.id, cron: result.cron }; | ||
| } | ||
|
|
||
| private async handleLiveScreenContextCapture( | ||
| params: Record<string, unknown>, | ||
| ): Promise<Record<string, unknown>> { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.