From a994821a162321c38ef98b408db28e2c88a2d5bb Mon Sep 17 00:00:00 2001 From: c020627 <1326713348@qq.com> Date: Thu, 20 Aug 2026 22:33:14 +0800 Subject: [PATCH] fix(runtime-host): drop unread scheduled-task backend persistence --- .../core/src/__tests__/scheduled-task.test.ts | 30 -------- packages/core/src/scheduled-task.ts | 11 --- .../__tests__/scheduled-task-protocol.test.ts | 69 +++++++++---------- .../src/protocol/scheduled-task.ts | 33 ++------- .../src/server/scheduled-task-coordinator.ts | 1 - 5 files changed, 41 insertions(+), 103 deletions(-) diff --git a/packages/core/src/__tests__/scheduled-task.test.ts b/packages/core/src/__tests__/scheduled-task.test.ts index 249164a722..e070779b0f 100644 --- a/packages/core/src/__tests__/scheduled-task.test.ts +++ b/packages/core/src/__tests__/scheduled-task.test.ts @@ -147,36 +147,6 @@ describe('scheduled-task catalog', () => { assert.deepEqual(result, { ok: false, message: 'Schedule must fire before expiresAt' }); }); - it('refuses to create an Automation on the retired backend', () => { - // #3211: this is create/update input, not a decoder — stored Automations - // are read back with JSON.parse and never reach here. Accepting `'fake'` - // would let a brand new Automation be written that can only fail later at - // activation. - const now = Date.UTC(2026, 0, 5, 8, 0, 0); - const execution = { - cwd: '/tmp/project', - llmConnectionSlug: 'anthropic', - model: 'claude-sonnet-4-5-20250929', - permissionMode: 'ask', - collaborationMode: 'agent', - orchestrationMode: 'default', - }; - const create = (backend: string) => - normalizeCreateScheduledTaskInput( - { - title: 'Nightly run', - intentBody: 'do the thing', - schedule: { kind: 'once', runAt: now + 60_000 }, - effect: { kind: 'agent_run', execution: { ...execution, backend } }, - createdBy: { kind: 'user' }, - }, - now, - ); - - assert.deepEqual(create('fake'), { ok: false, message: 'execution.backend is invalid' }); - assert.equal(create('ai-sdk').ok, true); - }); - it('rejects future recurrence anchors outside the scheduling horizon', () => { const now = Date.UTC(2026, 0, 5, 8, 0, 0); for (const schedule of [ diff --git a/packages/core/src/scheduled-task.ts b/packages/core/src/scheduled-task.ts index 8409b712a3..804cf01813 100644 --- a/packages/core/src/scheduled-task.ts +++ b/packages/core/src/scheduled-task.ts @@ -11,7 +11,6 @@ import { isOrchestrationMode, type OrchestrationMode } from './orchestration.js' import { isThinkingLevel, type ThinkingLevel } from './model-thinking.js'; import { isPermissionMode, type PermissionMode } from './permission.js'; import { isBotDeliveryProvider, type BotProvider } from './bot-chat-settings.js'; -import type { PersistedBackendKind } from './session.js'; export const SCHEDULED_TASK_TITLE_MAX_CHARS = 120; export const SCHEDULED_TASK_INTENT_MAX_CHARS = 8_000; @@ -52,7 +51,6 @@ export type ScheduledTaskEffect = export interface ScheduledTaskExecutionTemplate { readonly cwd: string; readonly projectId?: string | null; - readonly backend: PersistedBackendKind; readonly llmConnectionSlug: string; readonly model: string; readonly thinkingLevel?: ThinkingLevel; @@ -488,14 +486,6 @@ function normalizeExecution( ): ScheduledTaskNormalizeResult { if (!isObject(value)) return fail('agent_run requires execution template'); if (typeof value.cwd !== 'string' || !value.cwd.trim()) return fail('execution.cwd is required'); - if (typeof value.backend !== 'string') return fail('execution.backend is required'); - // Create/update input, not a decoder: stored Automations are read back with - // `JSON.parse` in scheduled-task-store.ts and never pass through here. So the - // retired `'fake'` is refused (#3211) — accepting it would let a brand new - // Automation be written that can only fail later at activation. - if (value.backend !== 'ai-sdk') { - return fail('execution.backend is invalid'); - } if (typeof value.llmConnectionSlug !== 'string' || !value.llmConnectionSlug.trim()) { return fail('execution.llmConnectionSlug is required'); } @@ -527,7 +517,6 @@ function normalizeExecution( value: { cwd: value.cwd.trim(), ...(projectId === undefined ? {} : { projectId }), - backend: value.backend, llmConnectionSlug: value.llmConnectionSlug.trim(), model: value.model.trim(), ...(value.thinkingLevel === undefined ? {} : { thinkingLevel: value.thinkingLevel }), diff --git a/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts b/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts index bf1f4b10ef..85eeabdeb9 100644 --- a/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts +++ b/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts @@ -12,7 +12,6 @@ import { authorizeRuntimeHostOperation, createRuntimeHostConnectionAuthority, } from '../server/connection-authority.js'; -import { decodeScheduledTask, decodeScheduledTaskMutateInput } from '../protocol/scheduled-task.js'; describe('ScheduledTask protocol', () => { test('requires Host-path authority only when a mutation submits a Host path', () => { @@ -35,39 +34,6 @@ describe('ScheduledTask protocol', () => { } }); - test('a retired backend decodes on the way out but not on the way in', () => { - // #3211: the same execution decoder serves both directions, and they carry - // different backend invariants. A stored Automation frozen by a build that - // shipped FakeBackend must stay readable; a live create/update may not - // introduce the retired value. - const retired = (effect: ScheduledTaskEffect): ScheduledTaskEffect => - effect.kind === 'agent_run' - ? { ...effect, execution: { ...effect.execution, backend: 'fake' } } - : effect; - - const stored = { ...scheduledTask('task-1'), effect: retired(agentRunEffect('project-1')) }; - assert.equal(decodeScheduledTask(stored).effect.kind, 'agent_run'); - - for (const input of [ - { - kind: 'create' as const, - input: { - title: 'Inspect workspace', - intentBody: 'Summarize the workspace.', - schedule: { kind: 'once' as const, runAt: 1 }, - effect: retired(agentRunEffect('project-1')), - }, - }, - { - kind: 'update' as const, - taskId: 'task-1', - patch: { effect: retired(agentRunEffect('project-1')) }, - }, - ]) { - assert.throws(() => decodeScheduledTaskMutateInput(input), /Invalid ScheduledTask backend/); - } - }); - test('accepts signal-only catalog changes', () => { const frame = { kind: 'scheduled-task.changed' as const, @@ -104,6 +70,40 @@ describe('ScheduledTask protocol', () => { /byte limit/, ); }); + + test('tolerates the legacy backend key on agent_run execution templates', () => { + // Older builds persisted `ScheduledTaskExecutionTemplate.backend`. The field + // is dropped from new writes, but stored records that still carry it must + // keep decoding, and the decoded template must not resurrect the field. + const template = { + cwd: '/workspace', + backend: 'fake', + llmConnectionSlug: 'openai', + model: 'gpt-5', + permissionMode: 'ask', + collaborationMode: 'agent', + orchestrationMode: 'default', + }; + const decoded = decodeScheduledTaskQueryResult({ + kind: 'task', + task: { + ...scheduledTask('legacy-backend'), + effect: { kind: 'agent_run', execution: template }, + }, + }); + assert.equal(decoded.kind, 'task'); + assert.ok(decoded.task !== null); + if (decoded.task.effect.kind !== 'agent_run') assert.fail('expected agent_run effect'); + assert.deepEqual(decoded.task.effect.execution, { + cwd: '/workspace', + llmConnectionSlug: 'openai', + model: 'gpt-5', + permissionMode: 'ask', + collaborationMode: 'agent', + orchestrationMode: 'default', + }); + assert.equal('backend' in decoded.task.effect.execution, false); + }); }); function createMutationFrame(projectId: string | null | undefined): RequestFrame { @@ -140,7 +140,6 @@ function agentRunEffect(projectId: string | null | undefined): ScheduledTaskEffe execution: { cwd: '/workspace', ...(projectId === undefined ? {} : { projectId }), - backend: 'ai-sdk', llmConnectionSlug: 'openai', model: 'gpt-5', permissionMode: 'ask', diff --git a/packages/runtime-host/src/protocol/scheduled-task.ts b/packages/runtime-host/src/protocol/scheduled-task.ts index 2768ae584f..59ae3fe13a 100644 --- a/packages/runtime-host/src/protocol/scheduled-task.ts +++ b/packages/runtime-host/src/protocol/scheduled-task.ts @@ -24,7 +24,6 @@ import { type UpdateScheduledTaskInput, } from '@maka/core/scheduled-task'; import { isThinkingLevel } from '@maka/core/model-thinking'; -import type { PersistedBackendKind } from '@maka/core/session'; import { requireCount, requireEncodedByteLimit, @@ -304,7 +303,7 @@ export function decodeScheduledTask(value: unknown): ScheduledTask { body: boundedText(intent.body, 'ScheduledTask intent body', SCHEDULED_TASK_INTENT_MAX_CHARS), }, schedule: decodeSchedule(task.schedule), - effect: decodeEffect(task.effect, 'stored'), + effect: decodeEffect(task.effect), status: task.status, nextFireAt: nullableCount(task.nextFireAt, 'ScheduledTask nextFireAt'), lastFireAt: nullableCount(task.lastFireAt, 'ScheduledTask lastFireAt'), @@ -341,7 +340,7 @@ function decodeCreateInput(value: unknown): Omit