mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-09 03:03:18 +08:00
Stamp and reclaim chat-created cron jobs for the current user.
Tool creates often left ownerEmpNo unassigned so the sidebar hid them while runs still fired; infer identity from session/cwd, claim legacy orphans on list, and keep cron rows visible without leaking run sessions into the workspace list. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d298c77f3f
commit
a0dc070372
7 changed files with 254 additions and 25 deletions
89
lib/tools.js
89
lib/tools.js
|
|
@ -15,6 +15,7 @@ import { resolveCreateAgentPreset } from './preset.js'
|
|||
import {
|
||||
assertCanAccessJob,
|
||||
filterJobsForIdentity,
|
||||
jobVisibleToIdentity,
|
||||
UNASSIGNED_OWNER,
|
||||
} from './ownership.js'
|
||||
|
||||
|
|
@ -127,18 +128,67 @@ export function resolveCreateCwd(args, exec, { peer = null } = {}) {
|
|||
return sessionCwd
|
||||
}
|
||||
|
||||
function forbidForeignJob(job, peer) {
|
||||
if (!peer?.botId) return job
|
||||
if (jobVisibleToPeer(job, peer)) return job
|
||||
const error = new Error('job not found or not owned by this chat')
|
||||
error.code = 'NOT_FOUND'
|
||||
throw error
|
||||
function callerSessionId(exec) {
|
||||
return String(
|
||||
exec?.agent?.session?.id
|
||||
|| exec?.agent?.session?.header?.id
|
||||
|| exec?.sessionId
|
||||
|| exec?.agent?.sessionId
|
||||
|| '',
|
||||
).trim()
|
||||
}
|
||||
|
||||
async function requireOwnedJob(service, id, peer) {
|
||||
function empNoFromUserWorkspacePath(cwd) {
|
||||
const norm = String(cwd || '').replace(/\\/g, '/')
|
||||
const match = norm.match(/\/user-workspaces\/([^/]+)(?:\/|$)/)
|
||||
return match ? decodeURIComponent(match[1]) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve web caller identity for ownership. IM peers keep conversation scoping.
|
||||
*/
|
||||
export function resolveToolIdentity(exec, service) {
|
||||
const uds = typeof service?.getUdsAuth === 'function' ? service.getUdsAuth() : null
|
||||
const sessionId = callerSessionId(exec)
|
||||
let empNo = null
|
||||
if (sessionId && uds?.getSessionOwner) {
|
||||
empNo = uds.getSessionOwner(sessionId)
|
||||
}
|
||||
if (!empNo) {
|
||||
empNo = empNoFromUserWorkspacePath(callerWorkingDirectory(exec))
|
||||
}
|
||||
if (!empNo) return null
|
||||
return {
|
||||
empNo: String(empNo),
|
||||
displayName: String(empNo),
|
||||
role: 'user',
|
||||
permissions: { canViewAllSessions: false },
|
||||
}
|
||||
}
|
||||
|
||||
function forbidForeignJob(job, peer, identity = null) {
|
||||
if (peer?.botId) {
|
||||
if (jobVisibleToPeer(job, peer)) return job
|
||||
const error = new Error('job not found or not owned by this chat')
|
||||
error.code = 'NOT_FOUND'
|
||||
throw error
|
||||
}
|
||||
if (identity?.empNo) {
|
||||
assertCanAccessJob(job, identity)
|
||||
return job
|
||||
}
|
||||
// No peer and no web identity: do not expose foreign jobs.
|
||||
if (job?.ownerEmpNo && job.ownerEmpNo !== UNASSIGNED_OWNER) {
|
||||
const error = new Error('job not found or not owned by this session')
|
||||
error.code = 'NOT_FOUND'
|
||||
throw error
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
async function requireOwnedJob(service, id, peer, identity = null) {
|
||||
const job = await service.getJob?.(id)
|
||||
if (job) return forbidForeignJob(job, peer)
|
||||
// Fallback when getJob is absent: list and find.
|
||||
if (job) return forbidForeignJob(job, peer, identity)
|
||||
const jobs = await service.listJobs()
|
||||
const hit = (jobs || []).find((row) => row.id === id)
|
||||
if (!hit) {
|
||||
|
|
@ -146,7 +196,7 @@ async function requireOwnedJob(service, id, peer) {
|
|||
error.code = 'NOT_FOUND'
|
||||
throw error
|
||||
}
|
||||
return forbidForeignJob(hit, peer)
|
||||
return forbidForeignJob(hit, peer, identity)
|
||||
}
|
||||
|
||||
export function callerModelSelection(exec) {
|
||||
|
|
@ -242,6 +292,7 @@ export function cronToolDefinitions(service, deps = {}) {
|
|||
const dshIm = getDshIm()
|
||||
try {
|
||||
const peer = await resolveCallerPeer(exec, dshIm)
|
||||
const identity = resolveToolIdentity(exec, service)
|
||||
const delivery = await resolveCreateDelivery(args, exec, { dshIm })
|
||||
const origin = await resolveCreateOrigin(args, exec, { dshIm })
|
||||
const agentPreset = await resolveCreateAgentPreset(args, exec, {
|
||||
|
|
@ -259,7 +310,7 @@ export function cronToolDefinitions(service, deps = {}) {
|
|||
delivery,
|
||||
...origin ? { origin } : {},
|
||||
agentPreset,
|
||||
})
|
||||
}, identity)
|
||||
return { job }
|
||||
} catch (error) {
|
||||
const tz = args.timezone || args.time_zone || 'Asia/Shanghai'
|
||||
|
|
@ -302,7 +353,8 @@ export function cronToolDefinitions(service, deps = {}) {
|
|||
async execute(args, exec) {
|
||||
aborted(exec)
|
||||
const peer = await resolveCallerPeer(exec, getDshIm())
|
||||
let jobs = await service.listJobs()
|
||||
const identity = resolveToolIdentity(exec, service)
|
||||
let jobs = await service.listJobs(identity)
|
||||
if (peer?.botId) jobs = jobs.filter((job) => jobVisibleToPeer(job, peer))
|
||||
if (args?.enabled_only === true) jobs = jobs.filter((job) => job.enabled !== false)
|
||||
return { jobs, count: jobs.length }
|
||||
|
|
@ -325,9 +377,10 @@ export function cronToolDefinitions(service, deps = {}) {
|
|||
async execute(args, exec) {
|
||||
aborted(exec)
|
||||
const peer = await resolveCallerPeer(exec, getDshIm())
|
||||
const identity = resolveToolIdentity(exec, service)
|
||||
const id = requireId(args)
|
||||
await requireOwnedJob(service, id, peer)
|
||||
const job = await service.pauseJob(id, false)
|
||||
await requireOwnedJob(service, id, peer, identity)
|
||||
const job = await service.pauseJob(id, false, identity)
|
||||
return { job }
|
||||
},
|
||||
},
|
||||
|
|
@ -348,9 +401,10 @@ export function cronToolDefinitions(service, deps = {}) {
|
|||
async execute(args, exec) {
|
||||
aborted(exec)
|
||||
const peer = await resolveCallerPeer(exec, getDshIm())
|
||||
const identity = resolveToolIdentity(exec, service)
|
||||
const id = requireId(args)
|
||||
await requireOwnedJob(service, id, peer)
|
||||
const job = await service.pauseJob(id, true)
|
||||
await requireOwnedJob(service, id, peer, identity)
|
||||
const job = await service.pauseJob(id, true, identity)
|
||||
return { job }
|
||||
},
|
||||
},
|
||||
|
|
@ -379,9 +433,10 @@ export function cronToolDefinitions(service, deps = {}) {
|
|||
async execute(args, exec) {
|
||||
aborted(exec)
|
||||
const peer = await resolveCallerPeer(exec, getDshIm())
|
||||
const identity = resolveToolIdentity(exec, service)
|
||||
const id = requireId(args)
|
||||
try {
|
||||
await requireOwnedJob(service, id, peer)
|
||||
await requireOwnedJob(service, id, peer, identity)
|
||||
await service.deleteJob(id)
|
||||
return { id, deleted: true }
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue