fix(bot-mode): show profile-owned cronjobs

This commit is contained in:
Andrew Walker
2026-08-17 14:45:37 -05:00
committed by Teknium
parent aa8ceed4b6
commit e3c4bfe9ab
4 changed files with 54 additions and 9 deletions
@@ -6004,10 +6004,11 @@ async function invalidateRoutineOwner(profile) {
function selectRoutineJobs(data, error, lastJobs, bot) {
const live = Array.isArray(data?.jobs) ? data.jobs : null
const all = live ?? (error ? lastJobs : [])
const scopedToBot = normalizedProfileName(data?.scoped) === normalizedProfileName(bot)
return {
live,
all,
jobs: all.filter(job => routineBot(job) === bot)
jobs: scopedToBot ? all : all.filter(job => routineBot(job) === bot)
}
}
@@ -43,6 +43,45 @@ test('unit: a live list is filtered to the current bot', () => {
assert.equal(shown[0].job_id, '1')
})
test('regression: a profile-scoped list shows every job owned by that bot profile', () => {
const profileJobs = [
{ name: 'ordinary profile cronjob', job_id: 'legacy' },
{ name: '[bot:ops] Bot Mode routine', job_id: 'routine' }
]
const view = load().__api.selectRoutineJobs({ jobs: profileJobs, scoped: 'ops' }, null, [], 'ops')
assert.deepEqual(
Array.from(view.jobs, job => job.job_id),
['legacy', 'routine']
)
})
test('compatibility: an unmarked list keeps tag filtering for older gateways', () => {
const profileJobs = [
{ name: 'ordinary launch-profile cronjob', job_id: 'legacy' },
{ name: '[bot:ops] Bot Mode routine', job_id: 'routine' }
]
const view = load().__api.selectRoutineJobs({ jobs: profileJobs }, null, [], 'ops')
assert.deepEqual(
Array.from(view.jobs, job => job.job_id),
['routine']
)
})
test('compatibility: a stale scope marker cannot leak another profile\'s jobs', () => {
const profileJobs = [
{ name: 'ordinary research cronjob', job_id: 'research' },
{ name: '[bot:ops] Bot Mode routine', job_id: 'routine' }
]
const view = load().__api.selectRoutineJobs({ jobs: profileJobs, scoped: 'research' }, null, [], 'ops')
assert.deepEqual(
Array.from(view.jobs, job => job.job_id),
['routine']
)
})
test('unit: a failed refresh keeps the last good list', () => {
const view = load().__api.selectRoutineJobs(undefined, new Error('down'), jobs, 'ops')
assert.equal(view.live, null)
+1
View File
@@ -47,6 +47,7 @@ def test_cron_manage_profile_reads_that_profiles_store(tmp_path, monkeypatch):
)
assert "result" in resp, resp
assert resp["result"]["scoped"] == "botA"
names = [j.get("name") for j in resp["result"]["jobs"]]
assert "botA-only-job" in names
+12 -8
View File
@@ -1699,15 +1699,19 @@ def _(rid, params: dict) -> dict:
if action == "list":
# Paused jobs are excluded by default, which reads as deletion in
# any UI with an enable/disable toggle — forward the flag.
return _ok(
rid,
json.loads(
cronjob(
action="list",
include_disabled=is_truthy_value(params.get("include_disabled", False)),
)
),
result = json.loads(
cronjob(
action="list",
include_disabled=is_truthy_value(params.get("include_disabled", False)),
)
)
# This marker proves the gateway honored the optional profile
# scope. New clients may therefore treat every returned job as
# owned by that profile; older gateways omit it, preserving the
# safe [bot:<name>] compatibility filter.
if profile:
result["scoped"] = profile
return _ok(rid, result)
if action == "add":
return _ok(
rid,