|
1 | 1 | import { listChannelPlugins } from "../../channels/plugins/index.js"; |
2 | 2 | import { parseAbsoluteTimeMs } from "../../cron/parse.js"; |
3 | 3 | import { resolveCronStaggerMs } from "../../cron/stagger.js"; |
4 | | -import type { CronJob, CronSchedule } from "../../cron/types.js"; |
| 4 | +import type { CronDeliveryPreview, CronJob, CronSchedule } from "../../cron/types.js"; |
5 | 5 | import { danger } from "../../globals.js"; |
6 | 6 | import { formatDurationHuman } from "../../infra/format-time/format-duration.ts"; |
7 | 7 | import { |
@@ -225,99 +225,26 @@ const formatStatus = (job: CronJob) => { |
225 | 225 | return job.state.lastStatus ?? "idle"; |
226 | 226 | }; |
227 | 227 |
|
228 | | -export type CronDeliveryPreview = { |
229 | | - label: string; |
230 | | - detail: string; |
231 | | -}; |
232 | | - |
233 | | -function formatTarget(channel?: string, to?: string | null): string { |
234 | | - if (!channel) { |
235 | | - return "last"; |
236 | | - } |
237 | | - if (to) { |
238 | | - return `${channel}:${to}`; |
239 | | - } |
240 | | - return channel; |
241 | | -} |
242 | | - |
243 | | -function formatDeliveryDetail(params: { |
244 | | - requestedChannel?: string; |
245 | | - resolved: boolean; |
246 | | - sessionKey?: string; |
247 | | - error?: string; |
248 | | -}): string { |
249 | | - if (params.requestedChannel === "last" || !params.requestedChannel) { |
250 | | - if (!params.resolved) { |
251 | | - return params.error |
252 | | - ? `last -> no route, will fail-closed: ${params.error}` |
253 | | - : "last -> no route, will fail-closed"; |
254 | | - } |
255 | | - return params.sessionKey |
256 | | - ? `resolved from last, session ${params.sessionKey}` |
257 | | - : "resolved from last, main session"; |
258 | | - } |
259 | | - return params.resolved ? "explicit" : (params.error ?? "unresolved"); |
260 | | -} |
261 | | - |
262 | | -export async function resolveCronDeliveryPreview(job: CronJob): Promise<CronDeliveryPreview> { |
263 | | - const { resolveCronDeliveryPlan } = await import("../../cron/delivery-plan.js"); |
264 | | - const plan = resolveCronDeliveryPlan(job); |
265 | | - if (!plan.requested && plan.mode === "none" && !job.delivery) { |
266 | | - return { label: "not requested", detail: "not requested" }; |
267 | | - } |
268 | | - if (plan.mode === "webhook") { |
269 | | - const target = plan.to ? `webhook:${plan.to}` : "webhook"; |
270 | | - return { label: target, detail: plan.to ? "webhook" : "webhook target missing" }; |
| 228 | +export function coerceCronDeliveryPreviews(value: unknown): Map<string, CronDeliveryPreview> { |
| 229 | + const previews = |
| 230 | + value && typeof value === "object" |
| 231 | + ? (value as { deliveryPreviews?: unknown }).deliveryPreviews |
| 232 | + : undefined; |
| 233 | + if (!previews || typeof previews !== "object") { |
| 234 | + return new Map(); |
271 | 235 | } |
272 | | - |
273 | | - const requestedChannel = plan.channel ?? "last"; |
274 | | - const [{ loadConfig }, { resolveDefaultAgentId }, { resolveDeliveryTarget }] = await Promise.all([ |
275 | | - import("../../config/config.js"), |
276 | | - import("../../agents/agent-scope-config.js"), |
277 | | - import("../../cron/isolated-agent/delivery-target.js"), |
278 | | - ]); |
279 | | - const cfg = loadConfig(); |
280 | | - const agentId = job.agentId?.trim() || resolveDefaultAgentId(cfg); |
281 | | - const resolved = await resolveDeliveryTarget( |
282 | | - cfg, |
283 | | - agentId, |
284 | | - { |
285 | | - channel: requestedChannel, |
286 | | - to: plan.to, |
287 | | - threadId: plan.threadId, |
288 | | - accountId: plan.accountId, |
289 | | - sessionKey: job.sessionKey, |
290 | | - }, |
291 | | - { dryRun: true }, |
292 | | - ); |
293 | | - if (!resolved.ok) { |
294 | | - return { |
295 | | - label: `${plan.mode} -> ${formatTarget(requestedChannel, plan.to ?? null)}`, |
296 | | - detail: formatDeliveryDetail({ |
297 | | - requestedChannel, |
298 | | - resolved: false, |
299 | | - sessionKey: job.sessionKey, |
300 | | - error: resolved.error.message, |
301 | | - }), |
302 | | - }; |
303 | | - } |
304 | | - return { |
305 | | - label: `${plan.mode} -> ${formatTarget(resolved.channel, resolved.to)}`, |
306 | | - detail: formatDeliveryDetail({ |
307 | | - requestedChannel, |
308 | | - resolved: true, |
309 | | - sessionKey: job.sessionKey, |
| 236 | + return new Map( |
| 237 | + Object.entries(previews as Record<string, unknown>).flatMap(([jobId, preview]) => { |
| 238 | + if (!preview || typeof preview !== "object") { |
| 239 | + return []; |
| 240 | + } |
| 241 | + const record = preview as { label?: unknown; detail?: unknown }; |
| 242 | + if (typeof record.label !== "string" || typeof record.detail !== "string") { |
| 243 | + return []; |
| 244 | + } |
| 245 | + return [[jobId, { label: record.label, detail: record.detail }]]; |
310 | 246 | }), |
311 | | - }; |
312 | | -} |
313 | | - |
314 | | -export async function resolveCronDeliveryPreviews( |
315 | | - jobs: CronJob[], |
316 | | -): Promise<Map<string, CronDeliveryPreview>> { |
317 | | - const entries = await Promise.all( |
318 | | - jobs.map(async (job) => [job.id, await resolveCronDeliveryPreview(job)] as const), |
319 | 247 | ); |
320 | | - return new Map(entries); |
321 | 248 | } |
322 | 249 |
|
323 | 250 | export function printCronList( |
@@ -421,8 +348,12 @@ export function printCronList( |
421 | 348 | } |
422 | 349 | } |
423 | 350 |
|
424 | | -export async function printCronShow(job: CronJob, runtime: RuntimeEnv = defaultRuntime) { |
425 | | - const preview = await resolveCronDeliveryPreview(job); |
| 351 | +export function printCronShow( |
| 352 | + job: CronJob, |
| 353 | + runtime: RuntimeEnv = defaultRuntime, |
| 354 | + opts?: { deliveryPreview?: CronDeliveryPreview }, |
| 355 | +) { |
| 356 | + const preview = opts?.deliveryPreview ?? { label: "-", detail: "unavailable" }; |
426 | 357 | runtime.log(`id: ${job.id}`); |
427 | 358 | runtime.log(`name: ${job.name}`); |
428 | 359 | runtime.log(`enabled: ${job.enabled ? "yes" : "no"}`); |
|
0 commit comments