Fix cron cwd for super_admin and harden IM channel job isolation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-10 10:00:59 +08:00
parent b35bfde557
commit b1bb0067a0
16 changed files with 714 additions and 49 deletions

View file

@ -378,9 +378,9 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
search: '搜索', searchPlaceholder: '搜索任务', searchClear: '清除搜索', searchEmpty: '没有匹配的任务。',
paused: '已暂停',
scheduleTz: '时区', cwd: '工作目录', timeout: '超时(分钟)',
cwdRecent: '最近使用的工作区', cwdCustom: '自定义路径…',
cwdRecent: '归属工作区(默认)', cwdCustom: '自定义路径…',
cwdPlaceholder: '/absolute/path',
cwdHint: '运行会在这个目录对应的工作区里开新会话。留空则用最近工作区。',
cwdHint: '运行会在这个目录对应的工作区里开新会话。留空则用任务归属者的工作区(不是最近打开的项目)。',
model: '模型',
modelDefault: '每次运行用当时的新会话默认',
modelHint: '定时任务会消耗这个模型的额度。指定后不会跟着聊天模型变。',
@ -399,7 +399,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
imTargetManual: '手动填写…',
imCatalogLoading: '正在加载投递目标…',
imCatalogUnavailable: '无法加载投递目标(需 dsh-im-ops ≥ops.24,且已配置投递目标)',
deliveryHint: '选 WhatsApp/IM 后从下拉选择已保存的投递目标;也可手动填写。目标在 IM 机器人 → 投递设置里创建。',
deliveryHint: '选 WhatsApp/IM 后从下拉选择已保存的投递目标(仅超管)。普通用户请在 IM 聊天里用 cron_create 建渠道任务。目标在 IM 机器人 → 投递设置里创建。',
mirrorToSession: '镜像回原会话',
mirrorToSessionHint: '开启后把每次运行摘要写入创建时的 WhatsApp/Web 会话(不新开模型轮次)。默认关闭。',
loginRequired: '登录后才能使用定时任务',
@ -424,9 +424,9 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
search: 'Search', searchPlaceholder: 'Search jobs', searchClear: 'Clear search', searchEmpty: 'No matching jobs.',
paused: 'Paused',
scheduleTz: 'Time zone', cwd: 'Working directory', timeout: 'Timeout (minutes)',
cwdRecent: 'Most recent workspace', cwdCustom: 'Custom path…',
cwdRecent: 'Owner workspace (default)', cwdCustom: 'Custom path…',
cwdPlaceholder: '/absolute/path',
cwdHint: 'Runs start a session in this workspace folder. Leave empty to use the most recent workspace.',
cwdHint: 'Runs start a session in this workspace folder. Leave empty to use the job owner provisioned workspace (not the most recently opened project).',
model: 'Model',
modelDefault: 'Use the New Session default at fire time',
modelHint: 'Scheduled runs consume this model\'s quota. A pinned model will not follow the chat selector.',
@ -445,7 +445,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
imTargetManual: 'Enter manually…',
imCatalogLoading: 'Loading delivery targets…',
imCatalogUnavailable: 'Cannot load targets (need dsh-im-ops ≥ops.24 with saved targets)',
deliveryHint: 'Pick a saved IM delivery target from the list, or enter botId/targetId manually. Create targets under IM bot → Delivery settings.',
deliveryHint: 'Pick a saved IM delivery target (super_admin only). Regular users should create channel jobs from the IM chat via cron_create. Create targets under IM bot → Delivery settings.',
mirrorToSession: 'Mirror into origin session',
mirrorToSessionHint: 'When enabled, append each run summary into the creating WhatsApp/Web session (no new model turn). Off by default.',
loginRequired: 'Sign in to use scheduled tasks',
@ -978,6 +978,14 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
return String(row?.cwd || '').trim()
}
/** Prefer provisioned viewer workspace over the currently open project. */
function preferredNewJobCwd(viewer, sessionCwd) {
const provisioned = String(viewer?.workspacePath || '').trim()
if (viewer?.mode === 'multi' && provisioned) return provisioned
if (provisioned) return provisioned
return String(sessionCwd || '').trim()
}
function cwdSelectValue(cwd, workspaces) {
const value = String(cwd || '').trim()
if (!value) return ''
@ -1185,10 +1193,12 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
onChange: (e) => setForm({ ...form, deliveryKind: e.target.value }),
},
h('option', { value: 'dsh' }, t('deliveryDsh')),
h('option', { value: 'im' }, t('deliveryIm')),
...(viewer?.mode !== 'multi' || viewer?.canViewAll
? [h('option', { value: 'im' }, t('deliveryIm'))]
: []),
),
),
form.deliveryKind === 'im'
form.deliveryKind === 'im' && (viewer?.mode !== 'multi' || viewer?.canViewAll)
? h(ImDeliveryFields, { t, form, setForm, imCatalog })
: null,
h('span', { className: 'dsh-ct-cwdHint' }, t('deliveryHint')),
@ -1384,7 +1394,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
setForm((prev) => {
let next = prev
if (!next.cwd) {
const cwd = currentWorkspacePath(faces)
const cwd = preferredNewJobCwd(viewer, currentWorkspacePath(faces))
if (cwd) next = { ...next, cwd }
}
if ((!next.provider || !next.model) && catalog.current?.provider && catalog.current?.model) {
@ -1395,7 +1405,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
}
return next
})
}, [cronMode, selection.type, catalog.current, presets.current])
}, [cronMode, selection.type, catalog.current, presets.current, viewer])
function expandJobPath(jobId) {
const job = jobs.find((row) => row.id === jobId)
@ -1414,7 +1424,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
skipAutoSelect.current = true
setSelection({ type: 'new' })
const next = emptyForm(undefined, catalog.current)
next.cwd = currentWorkspacePath(faces)
next.cwd = preferredNewJobCwd(viewer, currentWorkspacePath(faces))
setForm(next)
}