mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-11 09:00:45 +08:00
Mirror scheduled-run summaries into the origin effective session.
Pin origin at create time, keep isolated fire sessions for audit, and after IM/DSH delivery resume or open the origin chat to append the same summary for follow-ups. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2e690c30c8
commit
29d946f3dd
12 changed files with 527 additions and 26 deletions
46
lib/host.js
46
lib/host.js
|
|
@ -8,7 +8,7 @@ import { mkdir } from 'node:fs/promises'
|
|||
import { homedir } from 'node:os'
|
||||
import { basename, join } from 'node:path'
|
||||
import { claimOccurrence, executeClaimedRun, extractAssistantText, interruptActiveRuns, publicJob, settleRun, TITLE_PREFIX } from './fire.js'
|
||||
import { deliverRunToIm, mergeDeliveryMention } from './delivery.js'
|
||||
import { deliverRunToIm, mergeDeliveryMention, mirrorRunToSession, normalizeOrigin } from './delivery.js'
|
||||
import { workspaceVisibleIds } from './isolation.js'
|
||||
import { decideDispatch, nextFire, validateSchedule } from './scheduler.js'
|
||||
import {
|
||||
|
|
@ -112,6 +112,7 @@ function runView(run) {
|
|||
* @param {string} [options.filePath]
|
||||
* @param {object} [options.sessionPort] { createAndPrompt, archiveSession, waitForTurn }
|
||||
* @param {() => object|undefined} [options.getDshIm] soft-injected proactive IM API
|
||||
* @param {() => object|undefined} [options.getAgents] soft-injected agents service for session mirror
|
||||
* @param {{ warn?: Function, info?: Function }} [options.logger]
|
||||
* @param {number} [options.tickIntervalMs]
|
||||
*/
|
||||
|
|
@ -121,6 +122,7 @@ export function createHostService(options = {}) {
|
|||
const store = options.store || createStore({ filePath })
|
||||
const sessionPort = options.sessionPort || null
|
||||
const getDshIm = typeof options.getDshIm === 'function' ? options.getDshIm : () => undefined
|
||||
const getAgents = typeof options.getAgents === 'function' ? options.getAgents : () => undefined
|
||||
const logger = options.logger || console
|
||||
const tickIntervalMs = Number(options.tickIntervalMs) > 0 ? Number(options.tickIntervalMs) : 15_000
|
||||
let timer = null
|
||||
|
|
@ -144,6 +146,36 @@ export function createHostService(options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
async function maybeMirrorResult(job, run) {
|
||||
if (!job || !run || run.status === 'skipped') return
|
||||
try {
|
||||
const result = await mirrorRunToSession(job, run.summary || run.error || '', {
|
||||
dshIm: getDshIm(),
|
||||
getAgents,
|
||||
pluginName: PLUGIN_NAME,
|
||||
newId: () => randomUUID(),
|
||||
})
|
||||
if (result?.mirrored) {
|
||||
logger.info?.(`[dsh-ops-cron] mirrored run ${run.id} → session ${result.sessionId} via ${result.via} (${result.method}${result.resumed ? ', resumed' : ''})`)
|
||||
} else if (result?.reason && result.reason !== 'no_target') {
|
||||
logger.warn?.(`[dsh-ops-cron] mirror skipped for run ${run.id}: ${result.reason}${result.sessionId ? ` session=${result.sessionId}` : ''}${result.error ? ` (${result.error})` : ''}`)
|
||||
} else if (!result?.skipped) {
|
||||
logger.warn?.(`[dsh-ops-cron] mirror incomplete for run ${run.id}: ${result?.reason || 'unknown'}`)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn?.(`[dsh-ops-cron] mirror failed for job ${job.id}: ${error instanceof Error ? error.message : error}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function settleAndNotify(jobId, runId, claimedDecision) {
|
||||
const state = await snapshot()
|
||||
const run = (state.runs || []).find((row) => row.id === runId)
|
||||
const job = getJob(state, jobId)
|
||||
await maybeDeliverIm(job, run)
|
||||
await maybeMirrorResult(job, run)
|
||||
return { job: jobView(job), run: runView(run), decision: claimedDecision }
|
||||
}
|
||||
|
||||
async function createJob(input) {
|
||||
const t = now()
|
||||
let created
|
||||
|
|
@ -178,11 +210,15 @@ export function createHostService(options = {}) {
|
|||
delivery: patch.delivery !== undefined
|
||||
? mergeDeliveryMention(job.delivery, patch.delivery)
|
||||
: job.delivery,
|
||||
origin: patch.origin !== undefined
|
||||
? (normalizeOrigin(patch.origin) || undefined)
|
||||
: job.origin,
|
||||
}
|
||||
const record = createJobRecord(nextInput, current, t)
|
||||
record.createdAt = job.createdAt
|
||||
record.lastRunAt = job.lastRunAt
|
||||
record.lastStatus = job.lastStatus
|
||||
if (patch.origin === undefined && job.origin) record.origin = job.origin
|
||||
if (patch.schedule === undefined && patch.enabled === undefined) {
|
||||
record.nextRunAt = job.nextRunAt
|
||||
}
|
||||
|
|
@ -246,13 +282,9 @@ export function createHostService(options = {}) {
|
|||
}
|
||||
const settled = await store.mutate((current) => settleRun(current, run.id, terminal, now()))
|
||||
run = (settled.runs || []).find((row) => row.id === claimed.run.id)
|
||||
const job = getJob(settled, jobId)
|
||||
await maybeDeliverIm(job, run)
|
||||
return { job: jobView(job), run: runView(run), decision: claimed.decision }
|
||||
return settleAndNotify(jobId, run.id, claimed.decision)
|
||||
}
|
||||
const job = getJob(executed, jobId)
|
||||
await maybeDeliverIm(job, run)
|
||||
return { job: jobView(job), run: runView(run), decision: claimed.decision }
|
||||
return settleAndNotify(jobId, claimed.run.id, claimed.decision)
|
||||
}
|
||||
|
||||
async function tick() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue