Inherit agent presets and auto-bind IM delivery targets (0.1.7).

Cron jobs created from WhatsApp can omit bot/target ids and reuse chat overrides; sidebar/agent preset pins persist on fire.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-06 02:31:43 +08:00
parent 2f08136adf
commit 2e690c30c8
15 changed files with 830 additions and 82 deletions

View file

@ -8,7 +8,7 @@ import { mkdir } from 'node:fs/promises'
import { homedir } from 'node:os'
import { basename, join } from 'node:path'
import { claimOccurrence, executeClaimedRun, extractAssistantText, interruptActiveRuns, publicJob, settleRun, TITLE_PREFIX } from './fire.js'
import { deliverRunToIm } from './delivery.js'
import { deliverRunToIm, mergeDeliveryMention } from './delivery.js'
import { workspaceVisibleIds } from './isolation.js'
import { decideDispatch, nextFire, validateSchedule } from './scheduler.js'
import {
@ -174,7 +174,10 @@ export function createHostService(options = {}) {
provider: patch.provider !== undefined ? patch.provider : job.provider,
model: patch.model !== undefined ? patch.model : job.model,
reasoningEffort: patch.reasoningEffort !== undefined ? patch.reasoningEffort : job.reasoningEffort,
delivery: patch.delivery !== undefined ? patch.delivery : job.delivery,
agentPreset: patch.agentPreset !== undefined ? patch.agentPreset : job.agentPreset,
delivery: patch.delivery !== undefined
? mergeDeliveryMention(job.delivery, patch.delivery)
: job.delivery,
}
const record = createJobRecord(nextInput, current, t)
record.createdAt = job.createdAt
@ -375,6 +378,14 @@ export function createHostService(options = {}) {
return
}
if (path === `${API_PREFIX}/presets` && method === 'GET') {
const catalog = typeof sessionPort?.listPresets === 'function'
? await sessionPort.listPresets()
: { items: [], current: null }
write(200, { ok: true, ...catalog })
return
}
if (path === `${API_PREFIX}/workspaces` && method === 'GET') {
const workspaces = typeof sessionPort?.listWorkspaces === 'function'
? await sessionPort.listWorkspaces()
@ -383,6 +394,35 @@ export function createHostService(options = {}) {
return
}
if (path === `${API_PREFIX}/im-catalog` && method === 'GET') {
const dshIm = getDshIm()
if (!dshIm || typeof dshIm.listDeliveryCatalog !== 'function') {
write(200, {
ok: true,
available: false,
options: [],
hint: 'dsh-im-ops missing or outdated — install ≥ops.24 for delivery picker',
})
return
}
try {
const options = await dshIm.listDeliveryCatalog()
write(200, {
ok: true,
available: true,
options: Array.isArray(options) ? options : [],
})
} catch (error) {
write(200, {
ok: true,
available: false,
options: [],
hint: error instanceof Error ? error.message : String(error),
})
}
return
}
if (path === `${API_PREFIX}/jobs` && method === 'GET') {
const state = await snapshot()
write(200, { ok: true, jobs: listJobs(state).map(jobView) })
@ -766,6 +806,35 @@ export async function listModelChoices(ctx) {
return { groups, current: currentDefaultModel(ctx) }
}
export async function listPresetChoices(ctx) {
const presets = tryGet(ctx, 'agentPresets')
if (!presets || typeof presets.list !== 'function') {
return { items: [], current: null }
}
let items = []
try {
const listed = await presets.list()
const rows = Array.isArray(listed) ? listed : (Array.isArray(listed?.items) ? listed.items : [])
items = rows.map((row) => ({
id: String(row?.id || '').trim(),
name: String(row?.name || row?.displayName || row?.id || '').trim(),
})).filter((row) => row.id)
} catch {
items = []
}
let current = null
if (typeof presets.resolve === 'function') {
try {
const resolved = await presets.resolve()
const id = typeof resolved?.id === 'string' ? resolved.id.trim() : ''
if (id) current = { id, name: String(resolved?.name || id) }
} catch {
current = null
}
}
return { items, current }
}
export async function resolveJobModel(ctx, job) {
const provider = typeof job?.provider === 'string' ? job.provider.trim() : ''
const model = typeof job?.model === 'string' ? job.model.trim() : ''
@ -838,7 +907,7 @@ export function bindModelSelection(agentCtx, selection) {
})
}
async function composeCronAgent(ctx, selection) {
async function composeCronAgent(ctx, selection, job) {
const presets = tryGet(ctx, 'agentPresets')
if (!presets || typeof presets.resolve !== 'function' || typeof presets.mount !== 'function') {
return {
@ -847,7 +916,10 @@ async function composeCronAgent(ctx, selection) {
},
}
}
const resolved = await presets.resolve()
const wanted = typeof job?.agentPreset === 'string' ? job.agentPreset.trim() : ''
const resolved = wanted
? await presets.resolve(wanted)
: await presets.resolve()
const presetId = resolved?.id
return {
agentPreset: presetId,
@ -871,7 +943,7 @@ export function makeLiveSessionPort(ctx) {
const cwd = placement.cwd || defaultCwd()
await mkdir(cwd, { recursive: true })
const selection = await resolveJobModel(ctx, job)
const composition = await composeCronAgent(ctx, selection)
const composition = await composeCronAgent(ctx, selection, job)
const handle = await agents.create({
sessionId,
agentOptions: {
@ -935,6 +1007,9 @@ export function makeLiveSessionPort(ctx) {
async listModels() {
return listModelChoices(ctx)
},
async listPresets() {
return listPresetChoices(ctx)
},
async waitForTurn(sessionId) {
const entry = handles.get(sessionId)
const agents = tryGet(ctx, 'agents')