feat(cron): introduce delivery modes for isolated jobs
- Added support for new delivery modes in cron jobs: `announce`, `deliver`, and `none`. - Updated documentation to reflect changes in delivery options and usage examples. - Enhanced the cron job schema to include delivery configuration. - Refactored related CLI commands and UI components to accommodate the new delivery settings. - Improved handling of legacy delivery fields for backward compatibility. This update allows users to choose how output from isolated jobs is delivered, enhancing flexibility in job management.
This commit is contained in:
committed by
Peter Steinberger
parent
3a03e38378
commit
511c656cbc
@@ -25,9 +25,9 @@ export const DEFAULT_CRON_FORM: CronFormState = {
|
||||
wakeMode: "next-heartbeat",
|
||||
payloadKind: "systemEvent",
|
||||
payloadText: "",
|
||||
deliver: false,
|
||||
channel: "last",
|
||||
to: "",
|
||||
deliveryMode: "legacy",
|
||||
deliveryChannel: "last",
|
||||
deliveryTo: "",
|
||||
timeoutSeconds: "",
|
||||
postToMainPrefix: "",
|
||||
};
|
||||
|
||||
@@ -88,20 +88,8 @@ export function buildCronPayload(form: CronFormState) {
|
||||
const payload: {
|
||||
kind: "agentTurn";
|
||||
message: string;
|
||||
deliver?: boolean;
|
||||
channel?: string;
|
||||
to?: string;
|
||||
timeoutSeconds?: number;
|
||||
} = { kind: "agentTurn", message };
|
||||
if (form.deliver) {
|
||||
payload.deliver = true;
|
||||
}
|
||||
if (form.channel) {
|
||||
payload.channel = form.channel;
|
||||
}
|
||||
if (form.to.trim()) {
|
||||
payload.to = form.to.trim();
|
||||
}
|
||||
const timeoutSeconds = toNumber(form.timeoutSeconds, 0);
|
||||
if (timeoutSeconds > 0) {
|
||||
payload.timeoutSeconds = timeoutSeconds;
|
||||
@@ -118,6 +106,21 @@ export async function addCronJob(state: CronState) {
|
||||
try {
|
||||
const schedule = buildCronSchedule(state.cronForm);
|
||||
const payload = buildCronPayload(state.cronForm);
|
||||
const delivery =
|
||||
state.cronForm.sessionTarget === "isolated" &&
|
||||
state.cronForm.payloadKind === "agentTurn" &&
|
||||
state.cronForm.deliveryMode !== "legacy"
|
||||
? {
|
||||
mode:
|
||||
state.cronForm.deliveryMode === "announce"
|
||||
? "announce"
|
||||
: state.cronForm.deliveryMode === "deliver"
|
||||
? "deliver"
|
||||
: "none",
|
||||
channel: state.cronForm.deliveryChannel.trim() || "last",
|
||||
to: state.cronForm.deliveryTo.trim() || undefined,
|
||||
}
|
||||
: undefined;
|
||||
const agentId = state.cronForm.agentId.trim();
|
||||
const job = {
|
||||
name: state.cronForm.name.trim(),
|
||||
@@ -128,8 +131,11 @@ export async function addCronJob(state: CronState) {
|
||||
sessionTarget: state.cronForm.sessionTarget,
|
||||
wakeMode: state.cronForm.wakeMode,
|
||||
payload,
|
||||
delivery,
|
||||
isolation:
|
||||
state.cronForm.postToMainPrefix.trim() && state.cronForm.sessionTarget === "isolated"
|
||||
state.cronForm.postToMainPrefix.trim() &&
|
||||
state.cronForm.sessionTarget === "isolated" &&
|
||||
state.cronForm.deliveryMode === "legacy"
|
||||
? { postToMainPrefix: state.cronForm.postToMainPrefix.trim() }
|
||||
: undefined,
|
||||
};
|
||||
|
||||
@@ -66,5 +66,18 @@ export function formatCronPayload(job: CronJob) {
|
||||
if (p.kind === "systemEvent") {
|
||||
return `System: ${p.text}`;
|
||||
}
|
||||
return `Agent: ${p.message}`;
|
||||
const base = `Agent: ${p.message}`;
|
||||
const delivery = job.delivery;
|
||||
if (delivery && delivery.mode !== "none") {
|
||||
const target =
|
||||
delivery.channel || delivery.to
|
||||
? ` (${delivery.channel ?? "last"}${delivery.to ? ` -> ${delivery.to}` : ""})`
|
||||
: "";
|
||||
return `${base} · ${delivery.mode}${target}`;
|
||||
}
|
||||
if (!delivery && (p.deliver || p.to)) {
|
||||
const target = p.channel || p.to ? ` (${p.channel ?? "last"}${p.to ? ` -> ${p.to}` : ""})` : "";
|
||||
return `${base} · deliver${target}`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ export type CronPayload =
|
||||
thinking?: string;
|
||||
timeoutSeconds?: number;
|
||||
deliver?: boolean;
|
||||
provider?:
|
||||
channel?:
|
||||
| "last"
|
||||
| "whatsapp"
|
||||
| "telegram"
|
||||
@@ -453,6 +453,13 @@ export type CronPayload =
|
||||
bestEffortDeliver?: boolean;
|
||||
};
|
||||
|
||||
export type CronDelivery = {
|
||||
mode: "none" | "announce" | "deliver";
|
||||
channel?: string;
|
||||
to?: string;
|
||||
bestEffort?: boolean;
|
||||
};
|
||||
|
||||
export type CronIsolation = {
|
||||
postToMainPrefix?: string;
|
||||
};
|
||||
@@ -479,6 +486,7 @@ export type CronJob = {
|
||||
sessionTarget: CronSessionTarget;
|
||||
wakeMode: CronWakeMode;
|
||||
payload: CronPayload;
|
||||
delivery?: CronDelivery;
|
||||
isolation?: CronIsolation;
|
||||
state?: CronJobState;
|
||||
};
|
||||
|
||||
@@ -29,9 +29,9 @@ export type CronFormState = {
|
||||
wakeMode: "next-heartbeat" | "now";
|
||||
payloadKind: "systemEvent" | "agentTurn";
|
||||
payloadText: string;
|
||||
deliver: boolean;
|
||||
channel: string;
|
||||
to: string;
|
||||
deliveryMode: "legacy" | "none" | "announce" | "deliver";
|
||||
deliveryChannel: string;
|
||||
deliveryTo: string;
|
||||
timeoutSeconds: string;
|
||||
postToMainPrefix: string;
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ export type CronProps = {
|
||||
|
||||
function buildChannelOptions(props: CronProps): string[] {
|
||||
const options = ["last", ...props.channels.filter(Boolean)];
|
||||
const current = props.form.channel?.trim();
|
||||
const current = props.form.deliveryChannel?.trim();
|
||||
if (current && !options.includes(current)) {
|
||||
options.push(current);
|
||||
}
|
||||
@@ -197,77 +197,90 @@ export function renderCron(props: CronProps) {
|
||||
rows="4"
|
||||
></textarea>
|
||||
</label>
|
||||
${
|
||||
props.form.payloadKind === "agentTurn"
|
||||
? html`
|
||||
<div class="form-grid" style="margin-top: 12px;">
|
||||
<label class="field checkbox">
|
||||
<span>Deliver</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${props.form.deliver}
|
||||
@change=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
deliver: (e.target as HTMLInputElement).checked,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Channel</span>
|
||||
<select
|
||||
.value=${props.form.channel || "last"}
|
||||
@change=${(e: Event) =>
|
||||
${
|
||||
props.form.payloadKind === "agentTurn"
|
||||
? html`
|
||||
<div class="form-grid" style="margin-top: 12px;">
|
||||
<label class="field">
|
||||
<span>Delivery</span>
|
||||
<select
|
||||
.value=${props.form.deliveryMode}
|
||||
@change=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
channel: (e.target as HTMLSelectElement).value,
|
||||
deliveryMode: (e.target as HTMLSelectElement)
|
||||
.value as CronFormState["deliveryMode"],
|
||||
})}
|
||||
>
|
||||
${channelOptions.map(
|
||||
(channel) =>
|
||||
html`<option value=${channel}>
|
||||
${resolveChannelLabel(props, channel)}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>To</span>
|
||||
<input
|
||||
.value=${props.form.to}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({ to: (e.target as HTMLInputElement).value })}
|
||||
placeholder="+1555… or chat id"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Timeout (seconds)</span>
|
||||
<input
|
||||
.value=${props.form.timeoutSeconds}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
timeoutSeconds: (e.target as HTMLInputElement).value,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
${
|
||||
props.form.sessionTarget === "isolated"
|
||||
? html`
|
||||
<label class="field">
|
||||
<span>Post to main prefix</span>
|
||||
<input
|
||||
.value=${props.form.postToMainPrefix}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
postToMainPrefix: (e.target as HTMLInputElement).value,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
>
|
||||
<option value="legacy">Main summary (legacy)</option>
|
||||
<option value="announce">Announce summary</option>
|
||||
<option value="deliver">Deliver full output</option>
|
||||
<option value="none">None (internal)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Timeout (seconds)</span>
|
||||
<input
|
||||
.value=${props.form.timeoutSeconds}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
timeoutSeconds: (e.target as HTMLInputElement).value,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
${
|
||||
props.form.deliveryMode === "announce" || props.form.deliveryMode === "deliver"
|
||||
? html`
|
||||
<label class="field">
|
||||
<span>Channel</span>
|
||||
<select
|
||||
.value=${props.form.deliveryChannel || "last"}
|
||||
@change=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
deliveryChannel: (e.target as HTMLSelectElement).value,
|
||||
})}
|
||||
>
|
||||
${channelOptions.map(
|
||||
(channel) =>
|
||||
html`<option value=${channel}>
|
||||
${resolveChannelLabel(props, channel)}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>To</span>
|
||||
<input
|
||||
.value=${props.form.deliveryTo}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
deliveryTo: (e.target as HTMLInputElement).value,
|
||||
})}
|
||||
placeholder="+1555… or chat id"
|
||||
/>
|
||||
</label>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
props.form.sessionTarget === "isolated" && props.form.deliveryMode === "legacy"
|
||||
? html`
|
||||
<label class="field">
|
||||
<span>Post to main prefix</span>
|
||||
<input
|
||||
.value=${props.form.postToMainPrefix}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({
|
||||
postToMainPrefix: (e.target as HTMLInputElement).value,
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<div class="row" style="margin-top: 14px;">
|
||||
<button class="btn primary" ?disabled=${props.busy} @click=${props.onAdd}>
|
||||
${props.busy ? "Saving…" : "Add job"}
|
||||
|
||||
Reference in New Issue
Block a user