mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-16 23:33:51 +08:00
Add sidebar-only agentAccess lock for scheduled jobs.
Default remains allow; humans can deny agent pause/resume/retrigger/delete in the panel while tools never expose or set the field. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4472a9c74b
commit
ad6e90f197
7 changed files with 130 additions and 11 deletions
|
|
@ -430,6 +430,10 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
||||||
deliveryHint: '选 WhatsApp/IM 后从下拉选择已保存的投递目标(仅超管)。普通用户请在 IM 聊天里用 cron_create 建渠道任务。目标在 IM 机器人 → 投递设置里创建。',
|
deliveryHint: '选 WhatsApp/IM 后从下拉选择已保存的投递目标(仅超管)。普通用户请在 IM 聊天里用 cron_create 建渠道任务。目标在 IM 机器人 → 投递设置里创建。',
|
||||||
mirrorToSession: '镜像回原会话',
|
mirrorToSession: '镜像回原会话',
|
||||||
mirrorToSessionHint: '开启后把每次运行摘要写入创建时的 WhatsApp/Web 会话(不新开模型轮次)。默认关闭。',
|
mirrorToSessionHint: '开启后把每次运行摘要写入创建时的 WhatsApp/Web 会话(不新开模型轮次)。默认关闭。',
|
||||||
|
agentAccess: 'Agent 操作',
|
||||||
|
agentAccessAllow: '允许 Agent 用工具管理(默认)',
|
||||||
|
agentAccessDeny: '仅人工(禁止 Agent 暂停/恢复/重触发/删除)',
|
||||||
|
agentAccessHint: '仅侧栏可改;Agent 看不到此开关。默认保持现状(允许)。关掉后 Agent 仍可查询进度,但无法改任务。',
|
||||||
loginRequired: '登录后才能使用定时任务',
|
loginRequired: '登录后才能使用定时任务',
|
||||||
runNoSession: '这次运行没有可打开的会话',
|
runNoSession: '这次运行没有可打开的会话',
|
||||||
runOpenFailed: '打不开这次对话,会话可能已被删除',
|
runOpenFailed: '打不开这次对话,会话可能已被删除',
|
||||||
|
|
@ -498,6 +502,10 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
||||||
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.',
|
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',
|
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.',
|
mirrorToSessionHint: 'When enabled, append each run summary into the creating WhatsApp/Web session (no new model turn). Off by default.',
|
||||||
|
agentAccess: 'Agent control',
|
||||||
|
agentAccessAllow: 'Allow agents to manage via tools (default)',
|
||||||
|
agentAccessDeny: 'Human only (block agent pause/resume/retrigger/delete)',
|
||||||
|
agentAccessHint: 'Sidebar only; agents never see this toggle. Default keeps current behavior (allow). When denied, agents can still query progress but cannot mutate the job.',
|
||||||
loginRequired: 'Sign in to use scheduled tasks',
|
loginRequired: 'Sign in to use scheduled tasks',
|
||||||
runNoSession: 'This run has no session to open',
|
runNoSession: 'This run has no session to open',
|
||||||
runOpenFailed: 'Could not open this chat; the session may have been deleted',
|
runOpenFailed: 'Could not open this chat; the session may have been deleted',
|
||||||
|
|
@ -755,6 +763,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
||||||
imBotId: '',
|
imBotId: '',
|
||||||
imTargetId: '',
|
imTargetId: '',
|
||||||
mirrorToSession: false,
|
mirrorToSession: false,
|
||||||
|
agentAccess: 'allow',
|
||||||
labelsText: '',
|
labelsText: '',
|
||||||
persistKind: 'retain',
|
persistKind: 'retain',
|
||||||
archiveEndpoint: '',
|
archiveEndpoint: '',
|
||||||
|
|
@ -781,6 +790,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
||||||
imBotId: job.delivery?.botId || '',
|
imBotId: job.delivery?.botId || '',
|
||||||
imTargetId: job.delivery?.targetId || '',
|
imTargetId: job.delivery?.targetId || '',
|
||||||
mirrorToSession: job.mirrorToSession === true,
|
mirrorToSession: job.mirrorToSession === true,
|
||||||
|
agentAccess: job.agentAccess === 'deny' ? 'deny' : 'allow',
|
||||||
labelsText: formatLabelsText(job.labels),
|
labelsText: formatLabelsText(job.labels),
|
||||||
persistKind: persistHistoryKind(job),
|
persistKind: persistHistoryKind(job),
|
||||||
archiveEndpoint: job.persistHistory?.kind === 'archive' ? (job.persistHistory.endpoint || '') : '',
|
archiveEndpoint: job.persistHistory?.kind === 'archive' ? (job.persistHistory.endpoint || '') : '',
|
||||||
|
|
@ -1421,6 +1431,16 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
||||||
? h(ImDeliveryFields, { t, form, setForm, imCatalog })
|
? h(ImDeliveryFields, { t, form, setForm, imCatalog })
|
||||||
: null,
|
: null,
|
||||||
h('span', { className: 'dsh-ct-cwdHint' }, t('deliveryHint')),
|
h('span', { className: 'dsh-ct-cwdHint' }, t('deliveryHint')),
|
||||||
|
h('label', null, t('agentAccess'),
|
||||||
|
h('select', {
|
||||||
|
value: form.agentAccess === 'deny' ? 'deny' : 'allow',
|
||||||
|
onChange: (e) => setForm({ ...form, agentAccess: e.target.value }),
|
||||||
|
},
|
||||||
|
h('option', { value: 'allow' }, t('agentAccessAllow')),
|
||||||
|
h('option', { value: 'deny' }, t('agentAccessDeny')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h('span', { className: 'dsh-ct-cwdHint' }, t('agentAccessHint')),
|
||||||
h('label', { className: 'dsh-ct-check' },
|
h('label', { className: 'dsh-ct-check' },
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
|
@ -1689,6 +1709,7 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
||||||
? { kind: 'im', botId: form.imBotId, targetId: form.imTargetId }
|
? { kind: 'im', botId: form.imBotId, targetId: form.imTargetId }
|
||||||
: { kind: 'dsh' },
|
: { kind: 'dsh' },
|
||||||
mirrorToSession: form.mirrorToSession === true,
|
mirrorToSession: form.mirrorToSession === true,
|
||||||
|
agentAccess: form.agentAccess === 'deny' ? 'deny' : 'allow',
|
||||||
labels: parseLabelsText(form.labelsText),
|
labels: parseLabelsText(form.labelsText),
|
||||||
persistHistory: form.persistKind === 'forever'
|
persistHistory: form.persistKind === 'forever'
|
||||||
? { kind: 'forever' }
|
? { kind: 'forever' }
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ export function publicJob(job, runs = null) {
|
||||||
model: job.model || '',
|
model: job.model || '',
|
||||||
reasoningEffort: job.reasoningEffort || '',
|
reasoningEffort: job.reasoningEffort || '',
|
||||||
agentPreset: job.agentPreset || '',
|
agentPreset: job.agentPreset || '',
|
||||||
|
agentAccess: job.agentAccess === 'deny' ? 'deny' : 'allow',
|
||||||
ownerEmpNo: job.ownerEmpNo || '',
|
ownerEmpNo: job.ownerEmpNo || '',
|
||||||
ownerDisplayName: job.ownerDisplayName || '',
|
ownerDisplayName: job.ownerDisplayName || '',
|
||||||
delivery: job.delivery && job.delivery.kind === 'im'
|
delivery: job.delivery && job.delivery.kind === 'im'
|
||||||
|
|
|
||||||
|
|
@ -630,6 +630,9 @@ export function createHostService(options = {}) {
|
||||||
model: patch.model !== undefined ? patch.model : job.model,
|
model: patch.model !== undefined ? patch.model : job.model,
|
||||||
reasoningEffort: patch.reasoningEffort !== undefined ? patch.reasoningEffort : job.reasoningEffort,
|
reasoningEffort: patch.reasoningEffort !== undefined ? patch.reasoningEffort : job.reasoningEffort,
|
||||||
agentPreset: patch.agentPreset !== undefined ? patch.agentPreset : job.agentPreset,
|
agentPreset: patch.agentPreset !== undefined ? patch.agentPreset : job.agentPreset,
|
||||||
|
agentAccess: patch.agentAccess !== undefined
|
||||||
|
? patch.agentAccess
|
||||||
|
: (patch.agent_access !== undefined ? patch.agent_access : job.agentAccess),
|
||||||
delivery: patch.delivery !== undefined
|
delivery: patch.delivery !== undefined
|
||||||
? mergeDeliveryMention(job.delivery, patch.delivery)
|
? mergeDeliveryMention(job.delivery, patch.delivery)
|
||||||
: job.delivery,
|
: job.delivery,
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export {
|
||||||
workspaceVisibleIds,
|
workspaceVisibleIds,
|
||||||
} from './isolation.js'
|
} from './isolation.js'
|
||||||
export { wrapScheduledPrompt } from './prompt.js'
|
export { wrapScheduledPrompt } from './prompt.js'
|
||||||
export { normalizeJobModel, splitProviderModel } from './store.js'
|
export { normalizeJobModel, normalizeAgentAccess, agentMayMutateJob, splitProviderModel } from './store.js'
|
||||||
export {
|
export {
|
||||||
assertCanAccessJob,
|
assertCanAccessJob,
|
||||||
canViewAllJobs,
|
canViewAllJobs,
|
||||||
|
|
|
||||||
22
lib/store.js
22
lib/store.js
|
|
@ -77,6 +77,26 @@ export function newId() {
|
||||||
return randomUUID()
|
return randomUUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether agents may mutate this job via cron_* tools.
|
||||||
|
* Default allow (= current behavior). Only the sidebar should set deny.
|
||||||
|
* @param {unknown} input job or raw value
|
||||||
|
* @returns {'allow'|'deny'}
|
||||||
|
*/
|
||||||
|
export function normalizeAgentAccess(input) {
|
||||||
|
const raw = input && typeof input === 'object' && !Array.isArray(input)
|
||||||
|
? (input.agentAccess ?? input.agent_access)
|
||||||
|
: input
|
||||||
|
if (raw === false || raw === 0 || raw === 'deny' || raw === 'human' || raw === 'locked' || raw === 'off') {
|
||||||
|
return 'deny'
|
||||||
|
}
|
||||||
|
return 'allow'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function agentMayMutateJob(job) {
|
||||||
|
return normalizeAgentAccess(job) === 'allow'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repair provider/model pairs when LLMs or hosts pass a combined "provider/model"
|
* Repair provider/model pairs when LLMs or hosts pass a combined "provider/model"
|
||||||
* route in one or both fields (e.g. provider=model="zte/Qwen3-…" → zte + Qwen3-…).
|
* route in one or both fields (e.g. provider=model="zte/Qwen3-…" → zte + Qwen3-…).
|
||||||
|
|
@ -169,6 +189,7 @@ export function createJobRecord(input, state, now) {
|
||||||
input.persistHistory ?? input.persist_history,
|
input.persistHistory ?? input.persist_history,
|
||||||
settings.historyLimit,
|
settings.historyLimit,
|
||||||
)
|
)
|
||||||
|
const agentAccess = normalizeAgentAccess(input)
|
||||||
const job = {
|
const job = {
|
||||||
id: String(input.id || newId()),
|
id: String(input.id || newId()),
|
||||||
name,
|
name,
|
||||||
|
|
@ -182,6 +203,7 @@ export function createJobRecord(input, state, now) {
|
||||||
model: model.model,
|
model: model.model,
|
||||||
reasoningEffort: model.reasoningEffort,
|
reasoningEffort: model.reasoningEffort,
|
||||||
agentPreset,
|
agentPreset,
|
||||||
|
agentAccess,
|
||||||
delivery,
|
delivery,
|
||||||
mirrorToSession,
|
mirrorToSession,
|
||||||
ownerEmpNo,
|
ownerEmpNo,
|
||||||
|
|
|
||||||
51
lib/tools.js
51
lib/tools.js
|
|
@ -20,7 +20,7 @@ import {
|
||||||
jobVisibleToIdentity,
|
jobVisibleToIdentity,
|
||||||
UNASSIGNED_OWNER,
|
UNASSIGNED_OWNER,
|
||||||
} from './ownership.js'
|
} from './ownership.js'
|
||||||
import { splitProviderModel } from './store.js'
|
import { agentMayMutateJob, splitProviderModel } from './store.js'
|
||||||
|
|
||||||
const JOB_SCHEMA = {
|
const JOB_SCHEMA = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
@ -216,6 +216,20 @@ function jobLine(job) {
|
||||||
return `${job.name} [${life}${stuck}] ${sched} tz=${tz} cwd=${job.cwd || '(recent workspace)'} model=${model} ${delivery}${labels} next=${when} id=${job.id}`
|
return `${job.name} [${life}${stuck}] ${sched} tz=${tz} cwd=${job.cwd || '(recent workspace)'} model=${model} ${delivery}${labels} next=${when} id=${job.id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Strip human-only fields so agents cannot see or game panel-only controls. */
|
||||||
|
function forAgentView(job) {
|
||||||
|
if (!job || typeof job !== 'object') return job
|
||||||
|
const { agentAccess: _hidden, ...rest } = job
|
||||||
|
return rest
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertAgentMayMutate(job) {
|
||||||
|
if (agentMayMutateJob(job)) return
|
||||||
|
const error = new Error('this job is human-managed; change “Agent 操作” in the 定时任务 panel (agents cannot toggle it)')
|
||||||
|
error.code = 'AGENT_ACCESS_DENIED'
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
export function callerWorkingDirectory(exec) {
|
export function callerWorkingDirectory(exec) {
|
||||||
const session = exec?.agent?.session
|
const session = exec?.agent?.session
|
||||||
return String(session?.header?.cwd || session?.cwd || exec?.agent?.cwd || '').trim()
|
return String(session?.header?.cwd || session?.cwd || exec?.agent?.cwd || '').trim()
|
||||||
|
|
@ -458,7 +472,7 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
agentPreset,
|
agentPreset,
|
||||||
...monitorFieldsFromArgs(args),
|
...monitorFieldsFromArgs(args),
|
||||||
}, identity, { fromImPeer: !!peer?.botId })
|
}, identity, { fromImPeer: !!peer?.botId })
|
||||||
return { job }
|
return { job: forAgentView(job) }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const tz = args.timezone || args.time_zone || 'Asia/Shanghai'
|
const tz = args.timezone || args.time_zone || 'Asia/Shanghai'
|
||||||
const nowText = formatInZone(Date.now(), tz)
|
const nowText = formatInZone(Date.now(), tz)
|
||||||
|
|
@ -512,7 +526,8 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
let jobs = await service.listJobs(identity, Object.keys(query).length ? query : null)
|
let jobs = await service.listJobs(identity, Object.keys(query).length ? query : null)
|
||||||
if (peer?.botId) jobs = jobs.filter((job) => jobVisibleToPeer(job, peer))
|
if (peer?.botId) jobs = jobs.filter((job) => jobVisibleToPeer(job, peer))
|
||||||
if (args?.enabled_only === true) jobs = jobs.filter((job) => job.enabled !== false)
|
if (args?.enabled_only === true) jobs = jobs.filter((job) => job.enabled !== false)
|
||||||
return { jobs, count: jobs.length }
|
const visible = jobs.map(forAgentView)
|
||||||
|
return { jobs: visible, count: visible.length }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -565,6 +580,12 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
result.items = (result.items || []).filter((item) => jobVisibleToPeer(item.job, peer))
|
result.items = (result.items || []).filter((item) => jobVisibleToPeer(item.job, peer))
|
||||||
result.count = result.jobs.length
|
result.count = result.jobs.length
|
||||||
}
|
}
|
||||||
|
result.jobs = (result.jobs || []).map(forAgentView)
|
||||||
|
result.items = (result.items || []).map((item) => (
|
||||||
|
item && typeof item === 'object'
|
||||||
|
? { ...item, job: forAgentView(item.job) }
|
||||||
|
: item
|
||||||
|
))
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -665,6 +686,11 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
result.snapshots = (result.snapshots || []).filter((row) => jobVisibleToPeer(row.job, peer))
|
result.snapshots = (result.snapshots || []).filter((row) => jobVisibleToPeer(row.job, peer))
|
||||||
result.count = result.snapshots.length
|
result.count = result.snapshots.length
|
||||||
}
|
}
|
||||||
|
result.snapshots = (result.snapshots || []).map((row) => (
|
||||||
|
row && typeof row === 'object'
|
||||||
|
? { ...row, job: forAgentView(row.job) }
|
||||||
|
: row
|
||||||
|
))
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -687,9 +713,10 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
const peer = await resolveCallerPeer(exec, getDshIm())
|
const peer = await resolveCallerPeer(exec, getDshIm())
|
||||||
const identity = resolveToolIdentity(exec, service)
|
const identity = resolveToolIdentity(exec, service)
|
||||||
const id = requireId(args)
|
const id = requireId(args)
|
||||||
await requireOwnedJob(service, id, peer, identity)
|
const owned = await requireOwnedJob(service, id, peer, identity)
|
||||||
|
assertAgentMayMutate(owned)
|
||||||
const job = await service.pauseJob(id, false, identity)
|
const job = await service.pauseJob(id, false, identity)
|
||||||
return { job }
|
return { job: forAgentView(job) }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -711,9 +738,10 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
const peer = await resolveCallerPeer(exec, getDshIm())
|
const peer = await resolveCallerPeer(exec, getDshIm())
|
||||||
const identity = resolveToolIdentity(exec, service)
|
const identity = resolveToolIdentity(exec, service)
|
||||||
const id = requireId(args)
|
const id = requireId(args)
|
||||||
await requireOwnedJob(service, id, peer, identity)
|
const owned = await requireOwnedJob(service, id, peer, identity)
|
||||||
|
assertAgentMayMutate(owned)
|
||||||
const job = await service.pauseJob(id, true, identity)
|
const job = await service.pauseJob(id, true, identity)
|
||||||
return { job }
|
return { job: forAgentView(job) }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -760,10 +788,12 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
const identity = resolveToolIdentity(exec, service)
|
const identity = resolveToolIdentity(exec, service)
|
||||||
const id = String(args?.task_id || args?.id || '').trim()
|
const id = String(args?.task_id || args?.id || '').trim()
|
||||||
if (!id) throw new Error('task_id is required')
|
if (!id) throw new Error('task_id is required')
|
||||||
await requireOwnedJob(service, id, peer, identity)
|
const owned = await requireOwnedJob(service, id, peer, identity)
|
||||||
return service.retriggerJob(id, {
|
assertAgentMayMutate(owned)
|
||||||
|
const result = await service.retriggerJob(id, {
|
||||||
after_minutes: args?.after_minutes,
|
after_minutes: args?.after_minutes,
|
||||||
}, identity)
|
}, identity)
|
||||||
|
return { ...result, job: forAgentView(result.job) }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -794,7 +824,8 @@ export function cronToolDefinitions(service, deps = {}) {
|
||||||
const identity = resolveToolIdentity(exec, service)
|
const identity = resolveToolIdentity(exec, service)
|
||||||
const id = requireId(args)
|
const id = requireId(args)
|
||||||
try {
|
try {
|
||||||
await requireOwnedJob(service, id, peer, identity)
|
const owned = await requireOwnedJob(service, id, peer, identity)
|
||||||
|
assertAgentMayMutate(owned)
|
||||||
await service.deleteJob(id)
|
await service.deleteJob(id)
|
||||||
return { id, deleted: true }
|
return { id, deleted: true }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,47 @@ test('cron_create from IM peer stamps unassigned owner and forces session cwd',
|
||||||
assert.equal(created.job.delivery?.kind, 'im')
|
assert.equal(created.job.delivery?.kind, 'im')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('agentAccess deny is hidden from tools and blocks pause/delete', async (t) => {
|
||||||
|
const dir = await mkdtemp(join(tmpdir(), 'dsh-cron-tools-'))
|
||||||
|
t.after(() => rm(dir, { recursive: true, force: true }))
|
||||||
|
const service = createHostService({
|
||||||
|
filePath: join(dir, 'store.json'),
|
||||||
|
now: () => Date.now(),
|
||||||
|
sessionPort: {
|
||||||
|
async createAndPrompt() {
|
||||||
|
return { sessionId: 'tool-sess', status: 'succeeded', summary: 'ok' }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const tools = byName(cronToolDefinitions(service))
|
||||||
|
const created = await tools.cron_create.execute({
|
||||||
|
name: 'locked',
|
||||||
|
prompt: 'ping',
|
||||||
|
after_minutes: 30,
|
||||||
|
timezone: 'Asia/Shanghai',
|
||||||
|
}, {})
|
||||||
|
assert.equal(created.job.agentAccess, undefined)
|
||||||
|
assert.equal(created.job.id != null, true)
|
||||||
|
|
||||||
|
await service.updateJob(created.job.id, { agentAccess: 'deny' })
|
||||||
|
const listed = await tools.cron_list.execute({}, {})
|
||||||
|
const row = listed.jobs.find((job) => job.id === created.job.id)
|
||||||
|
assert.ok(row)
|
||||||
|
assert.equal(row.agentAccess, undefined)
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => tools.cron_pause.execute({ id: created.job.id }, {}),
|
||||||
|
(err) => err.code === 'AGENT_ACCESS_DENIED',
|
||||||
|
)
|
||||||
|
await assert.rejects(
|
||||||
|
() => tools.cron_delete.execute({ id: created.job.id }, {}),
|
||||||
|
(err) => err.code === 'AGENT_ACCESS_DENIED',
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpView = await service.getJob(created.job.id)
|
||||||
|
assert.equal(httpView.agentAccess, 'deny')
|
||||||
|
})
|
||||||
|
|
||||||
test('registerCronTools registers each definition and disposer unregisters', () => {
|
test('registerCronTools registers each definition and disposer unregisters', () => {
|
||||||
const registered = []
|
const registered = []
|
||||||
const ctx = {
|
const ctx = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue