fix(schedule): close concurrent durability gaps
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
scheduleView,
|
||||
} from './domain.ts'
|
||||
import { flushSchedulePersistence } from './persistence.ts'
|
||||
import { runScheduleTransaction } from './transaction.ts'
|
||||
import type {
|
||||
AfterScheduleRecord,
|
||||
PersistenceUncertainError,
|
||||
@@ -255,31 +256,33 @@ export function registerScheduleTools(
|
||||
if (exec.agent !== agent) return internalError()
|
||||
const invalid = validateCreateArgs(args)
|
||||
if (invalid !== undefined) return invalid
|
||||
const uncertain = await preflight(rootCtx, agent, 'create')
|
||||
if (uncertain !== undefined) return uncertain
|
||||
notifyDurableChange()
|
||||
const folded = foldForTool(agent)
|
||||
if (isToolError(folded)) return folded
|
||||
const id = allocateScheduleId(folded)
|
||||
let record: AfterScheduleRecord
|
||||
try {
|
||||
record = createAfterScheduleRecord(id, args.prompt, args.after_seconds, Date.now())
|
||||
} catch (error: unknown) {
|
||||
return error instanceof ScheduleInputError ? inputError(error) : internalError()
|
||||
}
|
||||
try {
|
||||
agent.session.append('schedule/change', {
|
||||
version: 1,
|
||||
operation: 'create',
|
||||
schedule: record,
|
||||
})
|
||||
} catch {
|
||||
return internalError()
|
||||
}
|
||||
const barrier = await preflight(rootCtx, agent, 'create', id)
|
||||
if (barrier !== undefined) return barrier
|
||||
notifyDurableChange()
|
||||
return scheduleView(record, Date.now())
|
||||
return runScheduleTransaction(agent, async () => {
|
||||
const uncertain = await preflight(rootCtx, agent, 'create')
|
||||
if (uncertain !== undefined) return uncertain
|
||||
notifyDurableChange()
|
||||
const folded = foldForTool(agent)
|
||||
if (isToolError(folded)) return folded
|
||||
const id = allocateScheduleId(folded)
|
||||
let record: AfterScheduleRecord
|
||||
try {
|
||||
record = createAfterScheduleRecord(id, args.prompt, args.after_seconds, Date.now())
|
||||
} catch (error: unknown) {
|
||||
return error instanceof ScheduleInputError ? inputError(error) : internalError()
|
||||
}
|
||||
try {
|
||||
agent.session.append('schedule/change', {
|
||||
version: 1,
|
||||
operation: 'create',
|
||||
schedule: record,
|
||||
})
|
||||
} catch {
|
||||
return internalError()
|
||||
}
|
||||
const barrier = await preflight(rootCtx, agent, 'create', id)
|
||||
if (barrier !== undefined) return barrier
|
||||
notifyDurableChange()
|
||||
return scheduleView(record, Date.now())
|
||||
})
|
||||
},
|
||||
presentCall: args => present('Create reminder', 'other', args.prompt),
|
||||
})))
|
||||
@@ -291,13 +294,15 @@ export function registerScheduleTools(
|
||||
output: { schema: LIST_OUTPUT_SCHEMA, render: renderValue },
|
||||
async execute(_args, exec): Promise<ScheduleListValue> {
|
||||
if (exec.agent !== agent) return internalError()
|
||||
const uncertain = await preflight(rootCtx, agent, 'list')
|
||||
if (uncertain !== undefined) return uncertain
|
||||
notifyDurableChange()
|
||||
const folded = foldForTool(agent)
|
||||
if (isToolError(folded)) return folded
|
||||
const now = Date.now()
|
||||
return folded.active.map(record => scheduleView(record, now))
|
||||
return runScheduleTransaction(agent, async () => {
|
||||
const uncertain = await preflight(rootCtx, agent, 'list')
|
||||
if (uncertain !== undefined) return uncertain
|
||||
notifyDurableChange()
|
||||
const folded = foldForTool(agent)
|
||||
if (isToolError(folded)) return folded
|
||||
const now = Date.now()
|
||||
return folded.active.map(record => scheduleView(record, now))
|
||||
})
|
||||
},
|
||||
presentCall: () => present('List reminders', 'read'),
|
||||
})))
|
||||
@@ -315,23 +320,25 @@ export function registerScheduleTools(
|
||||
}
|
||||
const id = ScheduleId(args.id)
|
||||
if (exec.agent !== agent) return internalError()
|
||||
const uncertain = await preflight(rootCtx, agent, 'delete', id)
|
||||
if (uncertain !== undefined) return uncertain
|
||||
notifyDurableChange()
|
||||
const folded = foldForTool(agent)
|
||||
if (isToolError(folded)) return folded
|
||||
if (!folded.active.some(record => record.id === id)) {
|
||||
return { id, deleted: false, code: 'schedule_not_found' }
|
||||
}
|
||||
try {
|
||||
agent.session.append('schedule/change', { version: 1, operation: 'delete', id })
|
||||
} catch {
|
||||
return internalError()
|
||||
}
|
||||
const barrier = await preflight(rootCtx, agent, 'delete', id)
|
||||
if (barrier !== undefined) return barrier
|
||||
notifyDurableChange()
|
||||
return { id, deleted: true }
|
||||
return runScheduleTransaction(agent, async () => {
|
||||
const uncertain = await preflight(rootCtx, agent, 'delete', id)
|
||||
if (uncertain !== undefined) return uncertain
|
||||
notifyDurableChange()
|
||||
const folded = foldForTool(agent)
|
||||
if (isToolError(folded)) return folded
|
||||
if (!folded.active.some(record => record.id === id)) {
|
||||
return { id, deleted: false, code: 'schedule_not_found' }
|
||||
}
|
||||
try {
|
||||
agent.session.append('schedule/change', { version: 1, operation: 'delete', id })
|
||||
} catch {
|
||||
return internalError()
|
||||
}
|
||||
const barrier = await preflight(rootCtx, agent, 'delete', id)
|
||||
if (barrier !== undefined) return barrier
|
||||
notifyDurableChange()
|
||||
return { id, deleted: true }
|
||||
})
|
||||
},
|
||||
presentCall: args => present('Delete reminder', 'other', args.id),
|
||||
})))
|
||||
|
||||
Reference in New Issue
Block a user