mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-09 00:43:22 +08:00
Isolate IM cron tools and trust-gate read APIs.
Scope list/pause/delete to the calling chat, bind IM delivery to that peer, ignore client job ids, require trusted Host for GETs, and wrap mirrored summaries as system reminders. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
29d946f3dd
commit
67238d746e
7 changed files with 222 additions and 29 deletions
|
|
@ -10,6 +10,7 @@ import {
|
|||
formatRunResultBody,
|
||||
deliverRunToIm,
|
||||
mirrorRunToSession,
|
||||
jobVisibleToPeer,
|
||||
} from '../lib/delivery.js'
|
||||
|
||||
test('normalizeDelivery defaults to dsh', () => {
|
||||
|
|
@ -307,10 +308,75 @@ test('mirrorRunToSession appends plugin notice without waking a turn', async ()
|
|||
assert.equal(appended[0][0], 'user/message')
|
||||
assert.match(appended[0][1].content[0].text, /日报/)
|
||||
assert.match(appended[0][1].content[0].text, /line one/)
|
||||
assert.match(appended[0][1].content[0].text, /<system-reminder>/)
|
||||
assert.equal(appended[0][1].source.kind, 'plugin')
|
||||
assert.deepEqual(appended[0][2], { surfaceOp: 'append' })
|
||||
})
|
||||
|
||||
test('matchTargetForPeer requires exact JID equality', () => {
|
||||
const targets = [{
|
||||
targetId: 'suffix-trap',
|
||||
kind: 'user',
|
||||
route: { jid: '8613800138000@s.whatsapp.net' },
|
||||
}]
|
||||
const peer = {
|
||||
kind: 'direct',
|
||||
conversationId: '13800138000@s.whatsapp.net',
|
||||
phone: '13800138000',
|
||||
}
|
||||
assert.equal(matchTargetForPeer(targets, peer), null)
|
||||
})
|
||||
|
||||
test('jobVisibleToPeer scopes IM ownership to origin conversation', () => {
|
||||
const peer = {
|
||||
botId: 'bot-a',
|
||||
conversationKey: 'group:120363@g.us:user:1@lid',
|
||||
conversationId: '120363@g.us',
|
||||
}
|
||||
assert.equal(jobVisibleToPeer({
|
||||
origin: {
|
||||
kind: 'im',
|
||||
peer: { botId: 'bot-a', conversationKey: 'group:120363@g.us:user:1@lid', conversationId: '120363@g.us' },
|
||||
},
|
||||
}, peer), true)
|
||||
assert.equal(jobVisibleToPeer({
|
||||
origin: {
|
||||
kind: 'im',
|
||||
peer: { botId: 'bot-a', conversationKey: 'group:other@g.us', conversationId: 'other@g.us' },
|
||||
},
|
||||
}, peer), false)
|
||||
assert.equal(jobVisibleToPeer({
|
||||
origin: { kind: 'web', sessionId: 'web-1' },
|
||||
}, peer), false)
|
||||
})
|
||||
|
||||
test('resolveCreateDelivery binds IM peers to the current chat', async () => {
|
||||
const peer = {
|
||||
botId: 'bot-a',
|
||||
conversationKey: 'direct:86138@s.whatsapp.net',
|
||||
conversationId: '86138@s.whatsapp.net',
|
||||
kind: 'direct',
|
||||
phone: '86138',
|
||||
}
|
||||
const delivery = await resolveCreateDelivery(
|
||||
{ delivery: 'im', im_bot_id: 'other-bot', im_target_id: 'other-target' },
|
||||
{ agent: { session: { id: 'sess-im' } } },
|
||||
{
|
||||
dshIm: {
|
||||
resolveSessionPeer: async () => peer,
|
||||
listTargets: async () => [{
|
||||
targetId: 'auto-dm',
|
||||
kind: 'user',
|
||||
route: { jid: '86138@s.whatsapp.net' },
|
||||
}],
|
||||
},
|
||||
},
|
||||
)
|
||||
assert.equal(delivery.kind, 'im')
|
||||
assert.equal(delivery.botId, 'bot-a')
|
||||
assert.equal(delivery.targetId, 'auto-dm')
|
||||
})
|
||||
|
||||
test('mirrorRunToSession resumes a cold origin session instead of create', async () => {
|
||||
const appended = []
|
||||
const resumed = []
|
||||
|
|
|
|||
|
|
@ -237,6 +237,30 @@ test('shipped HTTP handler: create, list, run-now, history', async (t) => {
|
|||
assert.deepEqual(visible.body.visible, ['other'])
|
||||
})
|
||||
|
||||
test('createJob ignores client-supplied id to prevent overwrite', async (t) => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'dsh-ops-cron-'))
|
||||
t.after(() => rm(dir, { recursive: true, force: true }))
|
||||
const service = createHostService({
|
||||
filePath: join(dir, 'store.json'),
|
||||
now: () => Date.parse('2026-08-24T01:00:00.000Z'),
|
||||
})
|
||||
const first = await service.createJob({
|
||||
name: 'keep-me',
|
||||
prompt: 'first',
|
||||
schedule: { kind: 'cron', expr: '0 9 * * *', timezone: 'Asia/Shanghai' },
|
||||
})
|
||||
const second = await service.createJob({
|
||||
id: first.id,
|
||||
name: 'attacker',
|
||||
prompt: 'overwrite?',
|
||||
schedule: { kind: 'cron', expr: '0 10 * * *', timezone: 'Asia/Shanghai' },
|
||||
})
|
||||
assert.notEqual(second.id, first.id)
|
||||
const listed = await service.listJobs()
|
||||
assert.equal(listed.find((job) => job.id === first.id)?.name, 'keep-me')
|
||||
assert.equal(listed.find((job) => job.id === second.id)?.name, 'attacker')
|
||||
})
|
||||
|
||||
test('POST /runs/:id/open reveals the run session id', async (t) => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'dsh-ops-cron-'))
|
||||
t.after(() => rm(dir, { recursive: true, force: true }))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue