fix: resolve add-editor drafts by path

This commit is contained in:
centdix
2026-05-19 18:07:00 +02:00
parent 3e5a6eb454
commit 10d1fde74f
3 changed files with 147 additions and 33 deletions
@@ -257,6 +257,46 @@ describe('global AI tools', () => {
expect(localStorage.getItem(`userdraft/w/${WORKSPACE}/script/f/scripts/open-editor`)).toBeNull()
})
it('lists and edits a new script draft stored under the empty add-editor key by its assigned path', async () => {
const content = 'export async function main() {\n\treturn 1\n}'
const handle = UserDraft.use<any>('script', '', { workspace: WORKSPACE })
handle.setDraftAndMeta(
{
path: 'u/admin/assigned_script',
summary: '',
language: 'bun',
content
},
{}
)
const raw = await callGlobalTool('list_workspace_items', {
types: ['script']
})
expect(JSON.parse(raw)).toEqual([
expect.objectContaining({
type: 'script',
path: 'u/admin/assigned_script',
isDraft: false
})
])
await callGlobalTool('edit_script', {
path: 'u/admin/assigned_script',
old_string: 'return 1',
new_string: 'return 2',
replace_all: false
})
expect(UserDraft.get<any>('script', '', { workspace: WORKSPACE })?.content).toContain(
'return 2'
)
expect(
UserDraft.get<any>('script', 'u/admin/assigned_script', { workspace: WORKSPACE })
).toBeUndefined()
})
it('overlays shared UserDraft entries in list_workspace_items', async () => {
await callGlobalTool('write_script', {
path: 'f/scripts/listed',
@@ -81,6 +81,7 @@ import {
deleteGlobalDraft,
getGlobalCurrentItem,
getGlobalDraft,
getGlobalDraftStoragePath,
listGlobalCurrentItems,
triggerKindToUserDraftKind
} from './userDraftAdapter'
@@ -659,11 +660,11 @@ function buildPersistedRunnable(
): PersistedRunnable {
const fields = input.staticInputs
? Object.fromEntries(
Object.entries(input.staticInputs).map(([k, v]) => [
k,
{ type: 'static', value: v, fieldType: 'object' }
])
)
Object.entries(input.staticInputs).map(([k, v]) => [
k,
{ type: 'static', value: v, fieldType: 'object' }
])
)
: (existing?.fields ?? {})
if (input.type === 'inline') {
@@ -1696,7 +1697,8 @@ async function writeScriptDraft(
const { workspace } = ctx
startDraftWrite(ctx, 'script', args.path)
const existingDraft = UserDraft.get<NewScript>('script', args.path, { workspace })
const draftStoragePath = getGlobalDraftStoragePath(workspace, 'script', args.path) ?? args.path
const existingDraft = UserDraft.get<NewScript>('script', draftStoragePath, { workspace })
const backendExists = existingDraft
? false
: await ScriptService.existsScriptByPath({ workspace, path: args.path })
@@ -1733,7 +1735,7 @@ async function writeScriptDraft(
}
}
UserDraft.save('script', args.path, draft, { workspace })
UserDraft.save('script', draftStoragePath, draft, { workspace })
return finishDraftWrite(
getRequiredGlobalDraft(workspace, 'script', args.path),
existingDraft !== undefined || backendExists,
@@ -1748,13 +1750,14 @@ async function writeFlowDraft(
const { workspace } = ctx
startDraftWrite(ctx, 'flow', args.path)
const draftStoragePath = getGlobalDraftStoragePath(workspace, 'flow', args.path) ?? args.path
const draftValue = args.flow
const value = structuredClone(draftValue.value)
if (draftValue.groups !== undefined && draftValue.groups !== null) {
value.groups = structuredClone(draftValue.groups)
}
const existingDraft = UserDraft.get<Flow>('flow', args.path, { workspace })
const existingDraft = UserDraft.get<Flow>('flow', draftStoragePath, { workspace })
const backendExists = existingDraft
? false
: await FlowService.existsFlowByPath({ workspace, path: args.path })
@@ -1790,7 +1793,7 @@ async function writeFlowDraft(
}
}
UserDraft.save('flow', args.path, draft, { workspace })
UserDraft.save('flow', draftStoragePath, draft, { workspace })
return finishDraftWrite(
getRequiredGlobalDraft(workspace, 'flow', args.path),
existingDraft !== undefined || backendExists,
@@ -149,7 +149,19 @@ function getItemSummary(value: unknown): string | undefined {
return ((value as { summary?: string | null } | undefined)?.summary ?? undefined) || undefined
}
function scriptDraftToWorkspaceItem(path: string, draft: NewScript, isDraft = true): WorkspaceItem {
function getItemPath(storagePath: string, value: unknown): string | undefined {
const valuePath = (value as { path?: string | null } | undefined)?.path
return valuePath?.trim() || storagePath || undefined
}
function scriptDraftToWorkspaceItem(
storagePath: string,
draft: NewScript,
isDraft = true
): WorkspaceItem | undefined {
const path = getItemPath(storagePath, draft)
if (!path) return undefined
return {
type: 'script',
path,
@@ -174,7 +186,7 @@ function getFlowSchema(value: unknown): Record<string, any> | null {
}
function flowDraftToWorkspaceItem(
path: string,
storagePath: string,
draft: unknown,
isDraft = true
): WorkspaceItem | undefined {
@@ -182,6 +194,8 @@ function flowDraftToWorkspaceItem(
const value = getFlowValue(draft.value) ?? getFlowValue(draft)
if (!value) return undefined
const path = getItemPath(storagePath, draft)
if (!path) return undefined
return {
type: 'flow',
@@ -199,11 +213,14 @@ function flowDraftToWorkspaceItem(
}
function appDraftToWorkspaceItem(
path: string,
storagePath: string,
draft: AppDraftValue,
isDraft = true
): WorkspaceItem {
): WorkspaceItem | undefined {
const value = normalizeAppDraftValue(draft)
const path = getItemPath(storagePath, value)
if (!path) return undefined
return {
type: 'app',
path,
@@ -214,10 +231,13 @@ function appDraftToWorkspaceItem(
}
function scheduleDraftToWorkspaceItem(
path: string,
storagePath: string,
draft: NewSchedule,
isDraft = true
): WorkspaceItem {
): WorkspaceItem | undefined {
const path = getItemPath(storagePath, draft)
if (!path) return undefined
return {
type: 'schedule',
path,
@@ -229,10 +249,13 @@ function scheduleDraftToWorkspaceItem(
function triggerDraftToWorkspaceItem(
kind: TriggerKind,
path: string,
storagePath: string,
draft: TriggerRequestBody,
isDraft = true
): WorkspaceItem {
): WorkspaceItem | undefined {
const path = getItemPath(storagePath, draft)
if (!path) return undefined
return {
type: 'trigger',
triggerKind: kind,
@@ -244,10 +267,13 @@ function triggerDraftToWorkspaceItem(
}
function resourceDraftToWorkspaceItem(
path: string,
storagePath: string,
draft: CreateResource,
isDraft = true
): WorkspaceItem {
): WorkspaceItem | undefined {
const path = getItemPath(storagePath, draft)
if (!path) return undefined
return {
type: 'resource',
path,
@@ -258,10 +284,13 @@ function resourceDraftToWorkspaceItem(
}
function variableDraftToWorkspaceItem(
path: string,
storagePath: string,
draft: CreateVariable,
isDraft = true
): WorkspaceItem {
): WorkspaceItem | undefined {
const path = getItemPath(storagePath, draft)
if (!path) return undefined
return {
type: 'variable',
path,
@@ -301,6 +330,32 @@ function sharedDraftEntryToWorkspaceItem(
}
}
type SharedDraftLookup = {
storagePath: string
value: unknown
}
function findSharedDraft(
workspace: string,
type: SharedWorkspaceItemType,
path: string,
triggerKind?: TriggerKind
): SharedDraftLookup | undefined {
const itemKind = sharedDraftKind(type, triggerKind)
if (!itemKind) return undefined
const direct = UserDraft.get(itemKind, path, { workspace })
if (direct !== undefined) return { storagePath: path, value: direct }
for (const entry of UserDraft.list({ workspace, itemKinds: [itemKind] })) {
if (getItemPath(entry.path, entry.value) === path) {
return { storagePath: entry.path, value: entry.value }
}
}
return undefined
}
function getSharedDraft(
workspace: string,
type: SharedWorkspaceItemType,
@@ -308,28 +363,31 @@ function getSharedDraft(
triggerKind?: TriggerKind,
isDraft = true
): WorkspaceItem | undefined {
const itemKind = sharedDraftKind(type, triggerKind)
if (!itemKind) return undefined
const draft = UserDraft.get(itemKind, path, { workspace })
if (draft === undefined) return undefined
const draft = findSharedDraft(workspace, type, path, triggerKind)
if (!draft) return undefined
switch (type) {
case 'script':
return scriptDraftToWorkspaceItem(path, draft as NewScript, isDraft)
return scriptDraftToWorkspaceItem(draft.storagePath, draft.value as NewScript, isDraft)
case 'flow':
return flowDraftToWorkspaceItem(path, draft, isDraft)
return flowDraftToWorkspaceItem(draft.storagePath, draft.value, isDraft)
case 'app':
return appDraftToWorkspaceItem(path, draft as AppDraftValue, isDraft)
return appDraftToWorkspaceItem(draft.storagePath, draft.value as AppDraftValue, isDraft)
case 'schedule':
return scheduleDraftToWorkspaceItem(path, draft as NewSchedule, isDraft)
return scheduleDraftToWorkspaceItem(draft.storagePath, draft.value as NewSchedule, isDraft)
case 'trigger':
return triggerKind
? triggerDraftToWorkspaceItem(triggerKind, path, draft as TriggerRequestBody, isDraft)
? triggerDraftToWorkspaceItem(
triggerKind,
draft.storagePath,
draft.value as TriggerRequestBody,
isDraft
)
: undefined
case 'resource':
return resourceDraftToWorkspaceItem(path, draft as CreateResource, isDraft)
return resourceDraftToWorkspaceItem(draft.storagePath, draft.value as CreateResource, isDraft)
case 'variable':
return variableDraftToWorkspaceItem(path, draft as CreateVariable, isDraft)
return variableDraftToWorkspaceItem(draft.storagePath, draft.value as CreateVariable, isDraft)
}
}
@@ -341,7 +399,8 @@ function deleteSharedDraft(
): void {
const itemKind = sharedDraftKind(type, triggerKind)
if (!itemKind) return
UserDraft.remove(itemKind, path, { workspace })
const draft = findSharedDraft(workspace, type, path, triggerKind)
UserDraft.remove(itemKind, draft?.storagePath ?? path, { workspace })
}
export function getGlobalDraft(
@@ -373,6 +432,18 @@ export function getGlobalCurrentItem(
return undefined
}
export function getGlobalDraftStoragePath(
workspace: string,
type: WorkspaceItemType,
path: string,
triggerKind?: TriggerKind
): string | undefined {
if (isSharedWorkspaceItemType(type)) {
return findSharedDraft(workspace, type, path, triggerKind)?.storagePath
}
return undefined
}
export function listGlobalDrafts(workspace: string): WorkspaceItem[] {
const drafts = new Map<string, WorkspaceItem>()