From 63cf68884938e1ebbb8abd89382ddc5116cfeb74 Mon Sep 17 00:00:00 2001 From: centdix Date: Wed, 20 May 2026 14:42:06 +0200 Subject: [PATCH] refactor: simplify script draft field preservation --- .../copilot/chat/global/core.test.ts | 71 +++++++++++++++++ .../components/copilot/chat/global/core.ts | 78 ++++++------------- 2 files changed, 96 insertions(+), 53 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/global/core.test.ts b/frontend/src/lib/components/copilot/chat/global/core.test.ts index 7068abea80..f62572db02 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.test.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.test.ts @@ -52,6 +52,7 @@ vi.mock('$lib/gen', async () => { ...actual, ScriptService: wrapService(actual.ScriptService, { existsScriptByPath: vi.fn(async () => false), + getScriptByPath: vi.fn(), listScripts: vi.fn(async () => []) }), FlowService: wrapService(actual.FlowService, { @@ -122,6 +123,7 @@ vi.mock('$lib/gen', async () => { import { globalTools, prepareGlobalUserMessage } from './core' import { deleteGlobalDraft, listGlobalDrafts } from './userDraftAdapter' import { UserDraft, __resetUserDraftForTesting } from '$lib/userDraft.svelte' +import { ScriptService } from '$lib/gen' import type { Tool, ToolCallbacks } from '../shared' const WORKSPACE = 'global-core-test' @@ -336,6 +338,75 @@ describe('global AI tools', () => { expect(localStorage.getItem(`userdraft/w/${WORKSPACE}/script/f/scripts/live-ai`)).not.toBeNull() }) + it('preserves NewScript metadata from existing scripts without copying backend read fields', async () => { + vi.mocked(ScriptService.existsScriptByPath).mockResolvedValueOnce(true) + vi.mocked(ScriptService.getScriptByPath).mockResolvedValueOnce({ + workspace_id: WORKSPACE, + hash: 'old-hash', + path: 'f/scripts/existing', + parent_hashes: ['older-hash'], + summary: 'Existing script', + description: 'Existing description', + content: 'export async function main() { return 1 }', + created_by: 'admin', + created_at: '2026-01-01T00:00:00Z', + archived: false, + deleted: false, + extra_perms: {}, + is_template: true, + language: 'bun', + kind: 'script', + starred: true, + has_draft: true, + schema: { + type: 'object', + properties: { + name: { type: 'string' } + } + }, + concurrent_limit: 3, + cache_ttl: 30, + cache_ignore_s3_path: true, + has_preprocessor: true, + assets: [{ path: 'asset.txt', kind: 's3object' }], + labels: ['important'] + } as any) + + await callGlobalTool('write_script', { + path: 'f/scripts/existing', + summary: 'AI update', + language: 'python3', + content: 'def main():\n return 2' + }) + + const draft = UserDraft.get('script', 'f/scripts/existing', { workspace: WORKSPACE }) + expect(draft).toMatchObject({ + path: 'f/scripts/existing', + parent_hash: 'old-hash', + summary: 'AI update', + description: 'Existing description', + content: 'def main():\n return 2', + language: 'python3', + is_template: true, + schema: { + properties: { + name: { type: 'string' } + } + }, + concurrent_limit: 3, + cache_ttl: 30, + cache_ignore_s3_path: true, + has_preprocessor: true, + assets: [{ path: 'asset.txt', kind: 's3object' }], + labels: ['important'] + }) + expect(draft).not.toHaveProperty('hash') + expect(draft).not.toHaveProperty('workspace_id') + expect(draft).not.toHaveProperty('created_by') + expect(draft).not.toHaveProperty('starred') + expect(draft).not.toHaveProperty('extra_perms') + }) + it('deleteGlobalDraft clears both persisted storage and any live handle state', async () => { const handle = UserDraft.use('script', 'f/scripts/delete-live', { workspace: WORKSPACE }) diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 0b351ca6d3..988e1a716f 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -15,7 +15,7 @@ import { VariableService, WebsocketTriggerService } from '$lib/gen' -import { $ScriptLang } from '$lib/gen/schemas.gen' +import { $NewScript, $ScriptLang } from '$lib/gen/schemas.gen' import type { AppWithLastVersion, Flow, @@ -1720,65 +1720,37 @@ function cloneIfDefined(value: T | undefined): T | undefined { } } -function buildScriptDraftFromBase( - base: Partial, - args: { path: string; summary?: string; language: ScriptLang; content: string }, - overrides: Partial = {} -): NewScript { - const draft: NewScript = { - path: args.path, - summary: args.summary ?? base.summary ?? '', - description: base.description ?? '', - content: args.content, - schema: cloneIfDefined(base.schema) ?? emptySchema(), - is_template: base.is_template ?? false, - language: args.language, - kind: base.kind ?? 'script' - } +const newScriptKeys = new Set(Object.keys($NewScript.properties) as Array) - const optionalKeys: (keyof NewScript)[] = [ - 'parent_hash', - 'lock', - 'tag', - 'draft_only', - 'envs', - 'concurrent_limit', - 'concurrency_time_window_s', - 'cache_ttl', - 'cache_ignore_s3_path', - 'dedicated_worker', - 'ws_error_handler_muted', - 'priority', - 'restart_unless_cancelled', - 'timeout', - 'delete_after_secs', - 'deployment_message', - 'concurrency_key', - 'debounce_key', - 'debounce_delay_s', - 'debounce_args_to_accumulate', - 'max_total_debouncing_time', - 'max_total_debounces_amount', - 'visible_to_runner_only', - 'auto_kind', - 'codebase', - 'has_preprocessor', - 'on_behalf_of_email', - 'preserve_on_behalf_of', - 'assets', - 'modules', - 'labels' - ] - - for (const key of optionalKeys) { - const cloned = cloneIfDefined(base[key] as never) +function pickNewScriptFields(base: Partial & Partial