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:
oliver 2026-09-08 16:19:59 +08:00
parent d298c77f3f
commit a0dc070372
7 changed files with 254 additions and 25 deletions

View file

@ -26,6 +26,7 @@ import {
import {
assertCanAccessJob,
canViewAllJobs,
claimUnassignedForViewer,
filterJobsForIdentity,
filterRunsForJobs,
migrateJobOwners,
@ -79,6 +80,40 @@ function parseCookieHeader(header, name) {
}
function empNoFromUserWorkspacePath(cwd) {
const norm = String(cwd || '').replace(/\\/g, '/')
const match = norm.match(/\/user-workspaces\/([^/]+)(?:\/|$)/)
return match ? decodeURIComponent(match[1]) : null
}
function inferIdentityFromJobInput(input, getUdsAuth) {
const uds = typeof getUdsAuth === 'function' ? getUdsAuth() : null
if (!uds) return null
const origin = input?.origin
const sessionId = origin?.kind === 'web' && typeof origin.sessionId === 'string'
? origin.sessionId.trim()
: ''
if (sessionId && typeof uds.getSessionOwner === 'function') {
const empNo = uds.getSessionOwner(sessionId)
if (empNo) {
return {
empNo: String(empNo),
displayName: String(empNo),
permissions: {},
}
}
}
const fromCwd = empNoFromUserWorkspacePath(input?.cwd)
if (fromCwd) {
return {
empNo: fromCwd,
displayName: fromCwd,
permissions: {},
}
}
return null
}
function browserEmpNo(request) {
const cookie = request?.headers?.cookie || ''
return parseCookieHeader(cookie, 'PORTALSSOUser')
@ -137,6 +172,8 @@ function sanitizeJobCwd(cwd, identity, getUdsAuth, { forOwnerEmpNo } = {}) {
: null
if (!raw) return ownerPath || ''
if (canViewAllJobs(identity)) return raw
// Without uds-auth, keep the caller cwd (tools may stamp owner from path only).
if (!uds) return raw
if (owner && uds?.isUserPath?.(owner, raw)) return raw
if (ownerPath) return ownerPath
return ''
@ -274,10 +311,11 @@ export function createHostService(options = {}) {
const t = now()
// Ignore client-supplied ids on create — otherwise POST/tools can overwrite.
const { id: _ignoredId, ownerEmpNo: _ignoreOwner, ...safeInput } = input && typeof input === 'object' ? input : {}
if (identity?.empNo) {
safeInput.ownerEmpNo = identity.empNo
safeInput.ownerDisplayName = identity.displayName || identity.empNo
safeInput.cwd = sanitizeJobCwd(safeInput.cwd, identity, getUdsAuth, { forOwnerEmpNo: identity.empNo })
let ownerIdentity = identity?.empNo ? identity : inferIdentityFromJobInput(safeInput, getUdsAuth)
if (ownerIdentity?.empNo) {
safeInput.ownerEmpNo = ownerIdentity.empNo
safeInput.ownerDisplayName = ownerIdentity.displayName || ownerIdentity.empNo
safeInput.cwd = sanitizeJobCwd(safeInput.cwd, ownerIdentity, getUdsAuth, { forOwnerEmpNo: ownerIdentity.empNo })
} else if (safeInput.ownerEmpNo) {
safeInput.ownerEmpNo = normalizeOwnerEmpNo(safeInput.ownerEmpNo)
} else {
@ -616,7 +654,15 @@ export function createHostService(options = {}) {
if (path === `${API_PREFIX}/jobs` && method === 'GET') {
const identity = await requireIdentity(req, write, getUdsAuth)
if (!identity) return
const state = await snapshot()
const uds = getUdsAuth()
const state = await withState((current) => {
const { state: next, changed } = claimUnassignedForViewer(current, identity, {
getSessionOwner: typeof uds?.getSessionOwner === 'function'
? (sessionId) => uds.getSessionOwner(sessionId)
: undefined,
})
return changed ? next : current
})
const jobs = filterJobsForIdentity(listJobs(state), identity).map(jobView)
write(200, {
ok: true,