Merge branch 'main' into qianfan
This commit is contained in:
@@ -65,6 +65,97 @@ describe("cron cli", () => {
|
||||
expect(params?.payload?.thinking).toBe("low");
|
||||
});
|
||||
|
||||
it("defaults isolated cron add to announce delivery", async () => {
|
||||
callGatewayFromCli.mockClear();
|
||||
|
||||
const { registerCronCli } = await import("./cron-cli.js");
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerCronCli(program);
|
||||
|
||||
await program.parseAsync(
|
||||
[
|
||||
"cron",
|
||||
"add",
|
||||
"--name",
|
||||
"Daily",
|
||||
"--cron",
|
||||
"* * * * *",
|
||||
"--session",
|
||||
"isolated",
|
||||
"--message",
|
||||
"hello",
|
||||
],
|
||||
{ from: "user" },
|
||||
);
|
||||
|
||||
const addCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.add");
|
||||
const params = addCall?.[2] as { delivery?: { mode?: string } };
|
||||
|
||||
expect(params?.delivery?.mode).toBe("announce");
|
||||
});
|
||||
|
||||
it("infers sessionTarget from payload when --session is omitted", async () => {
|
||||
callGatewayFromCli.mockClear();
|
||||
|
||||
const { registerCronCli } = await import("./cron-cli.js");
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerCronCli(program);
|
||||
|
||||
await program.parseAsync(
|
||||
["cron", "add", "--name", "Main reminder", "--cron", "* * * * *", "--system-event", "hi"],
|
||||
{ from: "user" },
|
||||
);
|
||||
|
||||
let addCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.add");
|
||||
let params = addCall?.[2] as { sessionTarget?: string; payload?: { kind?: string } };
|
||||
expect(params?.sessionTarget).toBe("main");
|
||||
expect(params?.payload?.kind).toBe("systemEvent");
|
||||
|
||||
callGatewayFromCli.mockClear();
|
||||
|
||||
await program.parseAsync(
|
||||
["cron", "add", "--name", "Isolated task", "--cron", "* * * * *", "--message", "hello"],
|
||||
{ from: "user" },
|
||||
);
|
||||
|
||||
addCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.add");
|
||||
params = addCall?.[2] as { sessionTarget?: string; payload?: { kind?: string } };
|
||||
expect(params?.sessionTarget).toBe("isolated");
|
||||
expect(params?.payload?.kind).toBe("agentTurn");
|
||||
});
|
||||
|
||||
it("supports --keep-after-run on cron add", async () => {
|
||||
callGatewayFromCli.mockClear();
|
||||
|
||||
const { registerCronCli } = await import("./cron-cli.js");
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerCronCli(program);
|
||||
|
||||
await program.parseAsync(
|
||||
[
|
||||
"cron",
|
||||
"add",
|
||||
"--name",
|
||||
"Keep me",
|
||||
"--at",
|
||||
"20m",
|
||||
"--session",
|
||||
"main",
|
||||
"--system-event",
|
||||
"hello",
|
||||
"--keep-after-run",
|
||||
],
|
||||
{ from: "user" },
|
||||
);
|
||||
|
||||
const addCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.add");
|
||||
const params = addCall?.[2] as { deleteAfterRun?: boolean };
|
||||
expect(params?.deleteAfterRun).toBe(false);
|
||||
});
|
||||
|
||||
it("sends agent id on cron add", async () => {
|
||||
callGatewayFromCli.mockClear();
|
||||
|
||||
@@ -213,20 +304,15 @@ describe("cron cli", () => {
|
||||
const updateCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.update");
|
||||
const patch = updateCall?.[2] as {
|
||||
patch?: {
|
||||
payload?: {
|
||||
kind?: string;
|
||||
message?: string;
|
||||
deliver?: boolean;
|
||||
channel?: string;
|
||||
to?: string;
|
||||
};
|
||||
payload?: { kind?: string; message?: string };
|
||||
delivery?: { mode?: string; channel?: string; to?: string };
|
||||
};
|
||||
};
|
||||
|
||||
expect(patch?.patch?.payload?.kind).toBe("agentTurn");
|
||||
expect(patch?.patch?.payload?.deliver).toBe(true);
|
||||
expect(patch?.patch?.payload?.channel).toBe("telegram");
|
||||
expect(patch?.patch?.payload?.to).toBe("19098680");
|
||||
expect(patch?.patch?.delivery?.mode).toBe("announce");
|
||||
expect(patch?.patch?.delivery?.channel).toBe("telegram");
|
||||
expect(patch?.patch?.delivery?.to).toBe("19098680");
|
||||
expect(patch?.patch?.payload?.message).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -242,11 +328,11 @@ describe("cron cli", () => {
|
||||
|
||||
const updateCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.update");
|
||||
const patch = updateCall?.[2] as {
|
||||
patch?: { payload?: { kind?: string; deliver?: boolean } };
|
||||
patch?: { payload?: { kind?: string }; delivery?: { mode?: string } };
|
||||
};
|
||||
|
||||
expect(patch?.patch?.payload?.kind).toBe("agentTurn");
|
||||
expect(patch?.patch?.payload?.deliver).toBe(false);
|
||||
expect(patch?.patch?.delivery?.mode).toBe("none");
|
||||
});
|
||||
|
||||
it("does not include undefined delivery fields when updating message", async () => {
|
||||
@@ -272,6 +358,7 @@ describe("cron cli", () => {
|
||||
to?: string;
|
||||
bestEffortDeliver?: boolean;
|
||||
};
|
||||
delivery?: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -283,6 +370,7 @@ describe("cron cli", () => {
|
||||
expect(patch?.patch?.payload).not.toHaveProperty("channel");
|
||||
expect(patch?.patch?.payload).not.toHaveProperty("to");
|
||||
expect(patch?.patch?.payload).not.toHaveProperty("bestEffortDeliver");
|
||||
expect(patch?.patch).not.toHaveProperty("delivery");
|
||||
});
|
||||
|
||||
it("includes delivery fields when explicitly provided with message", async () => {
|
||||
@@ -313,20 +401,16 @@ describe("cron cli", () => {
|
||||
const updateCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.update");
|
||||
const patch = updateCall?.[2] as {
|
||||
patch?: {
|
||||
payload?: {
|
||||
message?: string;
|
||||
deliver?: boolean;
|
||||
channel?: string;
|
||||
to?: string;
|
||||
};
|
||||
payload?: { message?: string };
|
||||
delivery?: { mode?: string; channel?: string; to?: string };
|
||||
};
|
||||
};
|
||||
|
||||
// Should include everything
|
||||
expect(patch?.patch?.payload?.message).toBe("Updated message");
|
||||
expect(patch?.patch?.payload?.deliver).toBe(true);
|
||||
expect(patch?.patch?.payload?.channel).toBe("telegram");
|
||||
expect(patch?.patch?.payload?.to).toBe("19098680");
|
||||
expect(patch?.patch?.delivery?.mode).toBe("announce");
|
||||
expect(patch?.patch?.delivery?.channel).toBe("telegram");
|
||||
expect(patch?.patch?.delivery?.to).toBe("19098680");
|
||||
});
|
||||
|
||||
it("includes best-effort delivery when provided with message", async () => {
|
||||
@@ -344,11 +428,15 @@ describe("cron cli", () => {
|
||||
|
||||
const updateCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.update");
|
||||
const patch = updateCall?.[2] as {
|
||||
patch?: { payload?: { message?: string; bestEffortDeliver?: boolean } };
|
||||
patch?: {
|
||||
payload?: { message?: string };
|
||||
delivery?: { bestEffort?: boolean; mode?: string };
|
||||
};
|
||||
};
|
||||
|
||||
expect(patch?.patch?.payload?.message).toBe("Updated message");
|
||||
expect(patch?.patch?.payload?.bestEffortDeliver).toBe(true);
|
||||
expect(patch?.patch?.delivery?.mode).toBe("announce");
|
||||
expect(patch?.patch?.delivery?.bestEffort).toBe(true);
|
||||
});
|
||||
|
||||
it("includes no-best-effort delivery when provided with message", async () => {
|
||||
@@ -366,10 +454,14 @@ describe("cron cli", () => {
|
||||
|
||||
const updateCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.update");
|
||||
const patch = updateCall?.[2] as {
|
||||
patch?: { payload?: { message?: string; bestEffortDeliver?: boolean } };
|
||||
patch?: {
|
||||
payload?: { message?: string };
|
||||
delivery?: { bestEffort?: boolean; mode?: string };
|
||||
};
|
||||
};
|
||||
|
||||
expect(patch?.patch?.payload?.message).toBe("Updated message");
|
||||
expect(patch?.patch?.payload?.bestEffortDeliver).toBe(false);
|
||||
expect(patch?.patch?.delivery?.mode).toBe("announce");
|
||||
expect(patch?.patch?.delivery?.bestEffort).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import { parsePositiveIntOrUndefined } from "../program/helpers.js";
|
||||
import {
|
||||
getCronChannelOptions,
|
||||
parseAtMs,
|
||||
parseAt,
|
||||
parseDurationMs,
|
||||
printCronList,
|
||||
warnIfCronSchedulerDisabled,
|
||||
@@ -68,8 +68,9 @@ export function registerCronAddCommand(cron: Command) {
|
||||
.option("--description <text>", "Optional description")
|
||||
.option("--disabled", "Create job disabled", false)
|
||||
.option("--delete-after-run", "Delete one-shot job after it succeeds", false)
|
||||
.option("--keep-after-run", "Keep one-shot job after it succeeds", false)
|
||||
.option("--agent <id>", "Agent id for this job")
|
||||
.option("--session <target>", "Session target (main|isolated)", "main")
|
||||
.option("--session <target>", "Session target (main|isolated)")
|
||||
.option("--wake <mode>", "Wake mode (now|next-heartbeat)", "next-heartbeat")
|
||||
.option("--at <when>", "Run once at time (ISO) or +duration (e.g. 20m)")
|
||||
.option("--every <duration>", "Run every duration (e.g. 10m, 1h)")
|
||||
@@ -80,26 +81,17 @@ export function registerCronAddCommand(cron: Command) {
|
||||
.option("--thinking <level>", "Thinking level for agent jobs (off|minimal|low|medium|high)")
|
||||
.option("--model <model>", "Model override for agent jobs (provider/model or alias)")
|
||||
.option("--timeout-seconds <n>", "Timeout seconds for agent jobs")
|
||||
.option(
|
||||
"--deliver",
|
||||
"Deliver agent output (required when using last-route delivery without --to)",
|
||||
false,
|
||||
)
|
||||
.option("--announce", "Announce summary to a chat (subagent-style)", false)
|
||||
.option("--deliver", "Deprecated (use --announce). Announces a summary to a chat.")
|
||||
.option("--no-deliver", "Disable announce delivery and skip main-session summary")
|
||||
.option("--channel <channel>", `Delivery channel (${getCronChannelOptions()})`, "last")
|
||||
.option(
|
||||
"--to <dest>",
|
||||
"Delivery destination (E.164, Telegram chatId, or Discord channel/user)",
|
||||
)
|
||||
.option("--best-effort-deliver", "Do not fail the job if delivery fails", false)
|
||||
.option("--post-prefix <prefix>", "Prefix for main-session post", "Cron")
|
||||
.option(
|
||||
"--post-mode <mode>",
|
||||
"What to post back to main for isolated jobs (summary|full)",
|
||||
"summary",
|
||||
)
|
||||
.option("--post-max-chars <n>", "Max chars when --post-mode=full (default 8000)", "8000")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts: GatewayRpcOpts & Record<string, unknown>) => {
|
||||
.action(async (opts: GatewayRpcOpts & Record<string, unknown>, cmd?: Command) => {
|
||||
try {
|
||||
const schedule = (() => {
|
||||
const at = typeof opts.at === "string" ? opts.at : "";
|
||||
@@ -110,11 +102,11 @@ export function registerCronAddCommand(cron: Command) {
|
||||
throw new Error("Choose exactly one schedule: --at, --every, or --cron");
|
||||
}
|
||||
if (at) {
|
||||
const atMs = parseAtMs(at);
|
||||
if (!atMs) {
|
||||
const atIso = parseAt(at);
|
||||
if (!atIso) {
|
||||
throw new Error("Invalid --at; use ISO time or duration like 20m");
|
||||
}
|
||||
return { kind: "at" as const, atMs };
|
||||
return { kind: "at" as const, at: atIso };
|
||||
}
|
||||
if (every) {
|
||||
const everyMs = parseDurationMs(every);
|
||||
@@ -130,12 +122,6 @@ export function registerCronAddCommand(cron: Command) {
|
||||
};
|
||||
})();
|
||||
|
||||
const sessionTargetRaw = typeof opts.session === "string" ? opts.session : "main";
|
||||
const sessionTarget = sessionTargetRaw.trim() || "main";
|
||||
if (sessionTarget !== "main" && sessionTarget !== "isolated") {
|
||||
throw new Error("--session must be main or isolated");
|
||||
}
|
||||
|
||||
const wakeModeRaw = typeof opts.wake === "string" ? opts.wake : "next-heartbeat";
|
||||
const wakeMode = wakeModeRaw.trim() || "next-heartbeat";
|
||||
if (wakeMode !== "now" && wakeMode !== "next-heartbeat") {
|
||||
@@ -147,6 +133,13 @@ export function registerCronAddCommand(cron: Command) {
|
||||
? sanitizeAgentId(opts.agent.trim())
|
||||
: undefined;
|
||||
|
||||
const hasAnnounce = Boolean(opts.announce) || opts.deliver === true;
|
||||
const hasNoDeliver = opts.deliver === false;
|
||||
const deliveryFlagCount = [hasAnnounce, hasNoDeliver].filter(Boolean).length;
|
||||
if (deliveryFlagCount > 1) {
|
||||
throw new Error("Choose at most one of --announce or --no-deliver");
|
||||
}
|
||||
|
||||
const payload = (() => {
|
||||
const systemEvent = typeof opts.systemEvent === "string" ? opts.systemEvent.trim() : "";
|
||||
const message = typeof opts.message === "string" ? opts.message.trim() : "";
|
||||
@@ -169,36 +162,46 @@ export function registerCronAddCommand(cron: Command) {
|
||||
: undefined,
|
||||
timeoutSeconds:
|
||||
timeoutSeconds && Number.isFinite(timeoutSeconds) ? timeoutSeconds : undefined,
|
||||
deliver: opts.deliver ? true : undefined,
|
||||
channel: typeof opts.channel === "string" ? opts.channel : "last",
|
||||
to: typeof opts.to === "string" && opts.to.trim() ? opts.to.trim() : undefined,
|
||||
bestEffortDeliver: opts.bestEffortDeliver ? true : undefined,
|
||||
};
|
||||
})();
|
||||
|
||||
const optionSource =
|
||||
typeof cmd?.getOptionValueSource === "function"
|
||||
? (name: string) => cmd.getOptionValueSource(name)
|
||||
: () => undefined;
|
||||
const sessionSource = optionSource("session");
|
||||
const sessionTargetRaw = typeof opts.session === "string" ? opts.session.trim() : "";
|
||||
const inferredSessionTarget = payload.kind === "agentTurn" ? "isolated" : "main";
|
||||
const sessionTarget =
|
||||
sessionSource === "cli" ? sessionTargetRaw || "" : inferredSessionTarget;
|
||||
if (sessionTarget !== "main" && sessionTarget !== "isolated") {
|
||||
throw new Error("--session must be main or isolated");
|
||||
}
|
||||
|
||||
if (opts.deleteAfterRun && opts.keepAfterRun) {
|
||||
throw new Error("Choose --delete-after-run or --keep-after-run, not both");
|
||||
}
|
||||
|
||||
if (sessionTarget === "main" && payload.kind !== "systemEvent") {
|
||||
throw new Error("Main jobs require --system-event (systemEvent).");
|
||||
}
|
||||
if (sessionTarget === "isolated" && payload.kind !== "agentTurn") {
|
||||
throw new Error("Isolated jobs require --message (agentTurn).");
|
||||
}
|
||||
if (
|
||||
(opts.announce || typeof opts.deliver === "boolean") &&
|
||||
(sessionTarget !== "isolated" || payload.kind !== "agentTurn")
|
||||
) {
|
||||
throw new Error("--announce/--no-deliver require --session isolated.");
|
||||
}
|
||||
|
||||
const isolation =
|
||||
sessionTarget === "isolated"
|
||||
? {
|
||||
postToMainPrefix:
|
||||
typeof opts.postPrefix === "string" && opts.postPrefix.trim()
|
||||
? opts.postPrefix.trim()
|
||||
: "Cron",
|
||||
postToMainMode:
|
||||
opts.postMode === "full" || opts.postMode === "summary"
|
||||
? opts.postMode
|
||||
: undefined,
|
||||
postToMainMaxChars:
|
||||
typeof opts.postMaxChars === "string" && /^\d+$/.test(opts.postMaxChars)
|
||||
? Number.parseInt(opts.postMaxChars, 10)
|
||||
: undefined,
|
||||
}
|
||||
const deliveryMode =
|
||||
sessionTarget === "isolated" && payload.kind === "agentTurn"
|
||||
? hasAnnounce
|
||||
? "announce"
|
||||
: hasNoDeliver
|
||||
? "none"
|
||||
: "announce"
|
||||
: undefined;
|
||||
|
||||
const nameRaw = typeof opts.name === "string" ? opts.name : "";
|
||||
@@ -216,13 +219,23 @@ export function registerCronAddCommand(cron: Command) {
|
||||
name,
|
||||
description,
|
||||
enabled: !opts.disabled,
|
||||
deleteAfterRun: Boolean(opts.deleteAfterRun),
|
||||
deleteAfterRun: opts.deleteAfterRun ? true : opts.keepAfterRun ? false : undefined,
|
||||
agentId,
|
||||
schedule,
|
||||
sessionTarget,
|
||||
wakeMode,
|
||||
payload,
|
||||
isolation,
|
||||
delivery: deliveryMode
|
||||
? {
|
||||
mode: deliveryMode,
|
||||
channel:
|
||||
typeof opts.channel === "string" && opts.channel.trim()
|
||||
? opts.channel.trim()
|
||||
: undefined,
|
||||
to: typeof opts.to === "string" && opts.to.trim() ? opts.to.trim() : undefined,
|
||||
bestEffort: opts.bestEffortDeliver ? true : undefined,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const res = await callGatewayFromCli("cron.add", opts, params);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { defaultRuntime } from "../../runtime.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import {
|
||||
getCronChannelOptions,
|
||||
parseAtMs,
|
||||
parseAt,
|
||||
parseDurationMs,
|
||||
warnIfCronSchedulerDisabled,
|
||||
} from "./shared.js";
|
||||
@@ -46,11 +46,9 @@ export function registerCronEditCommand(cron: Command) {
|
||||
.option("--thinking <level>", "Thinking level for agent jobs")
|
||||
.option("--model <model>", "Model override for agent jobs")
|
||||
.option("--timeout-seconds <n>", "Timeout seconds for agent jobs")
|
||||
.option(
|
||||
"--deliver",
|
||||
"Deliver agent output (required when using last-route delivery without --to)",
|
||||
)
|
||||
.option("--no-deliver", "Disable delivery")
|
||||
.option("--announce", "Announce summary to a chat (subagent-style)")
|
||||
.option("--deliver", "Deprecated (use --announce). Announces a summary to a chat.")
|
||||
.option("--no-deliver", "Disable announce delivery")
|
||||
.option("--channel <channel>", `Delivery channel (${getCronChannelOptions()})`)
|
||||
.option(
|
||||
"--to <dest>",
|
||||
@@ -58,7 +56,6 @@ export function registerCronEditCommand(cron: Command) {
|
||||
)
|
||||
.option("--best-effort-deliver", "Do not fail job if delivery fails")
|
||||
.option("--no-best-effort-deliver", "Fail job when delivery fails")
|
||||
.option("--post-prefix <prefix>", "Prefix for summary system event")
|
||||
.action(async (id, opts) => {
|
||||
try {
|
||||
if (opts.session === "main" && opts.message) {
|
||||
@@ -71,8 +68,8 @@ export function registerCronEditCommand(cron: Command) {
|
||||
"Isolated jobs cannot use --system-event; use --message or --session main.",
|
||||
);
|
||||
}
|
||||
if (opts.session === "main" && typeof opts.postPrefix === "string") {
|
||||
throw new Error("--post-prefix only applies to isolated jobs.");
|
||||
if (opts.announce && typeof opts.deliver === "boolean") {
|
||||
throw new Error("Choose --announce or --no-deliver (not multiple).");
|
||||
}
|
||||
|
||||
const patch: Record<string, unknown> = {};
|
||||
@@ -121,11 +118,11 @@ export function registerCronEditCommand(cron: Command) {
|
||||
throw new Error("Choose at most one schedule change");
|
||||
}
|
||||
if (opts.at) {
|
||||
const atMs = parseAtMs(String(opts.at));
|
||||
if (!atMs) {
|
||||
const atIso = parseAt(String(opts.at));
|
||||
if (!atIso) {
|
||||
throw new Error("Invalid --at");
|
||||
}
|
||||
patch.schedule = { kind: "at", atMs };
|
||||
patch.schedule = { kind: "at", at: atIso };
|
||||
} else if (opts.every) {
|
||||
const everyMs = parseDurationMs(String(opts.every));
|
||||
if (!everyMs) {
|
||||
@@ -151,15 +148,17 @@ export function registerCronEditCommand(cron: Command) {
|
||||
? Number.parseInt(String(opts.timeoutSeconds), 10)
|
||||
: undefined;
|
||||
const hasTimeoutSeconds = Boolean(timeoutSeconds && Number.isFinite(timeoutSeconds));
|
||||
const hasDeliveryModeFlag = opts.announce || typeof opts.deliver === "boolean";
|
||||
const hasDeliveryTarget = typeof opts.channel === "string" || typeof opts.to === "string";
|
||||
const hasBestEffort = typeof opts.bestEffortDeliver === "boolean";
|
||||
const hasAgentTurnPatch =
|
||||
typeof opts.message === "string" ||
|
||||
Boolean(model) ||
|
||||
Boolean(thinking) ||
|
||||
hasTimeoutSeconds ||
|
||||
typeof opts.deliver === "boolean" ||
|
||||
typeof opts.channel === "string" ||
|
||||
typeof opts.to === "string" ||
|
||||
typeof opts.bestEffortDeliver === "boolean";
|
||||
hasDeliveryModeFlag ||
|
||||
hasDeliveryTarget ||
|
||||
hasBestEffort;
|
||||
if (hasSystemEventPatch && hasAgentTurnPatch) {
|
||||
throw new Error("Choose at most one payload change");
|
||||
}
|
||||
@@ -174,22 +173,29 @@ export function registerCronEditCommand(cron: Command) {
|
||||
assignIf(payload, "model", model, Boolean(model));
|
||||
assignIf(payload, "thinking", thinking, Boolean(thinking));
|
||||
assignIf(payload, "timeoutSeconds", timeoutSeconds, hasTimeoutSeconds);
|
||||
assignIf(payload, "deliver", opts.deliver, typeof opts.deliver === "boolean");
|
||||
assignIf(payload, "channel", opts.channel, typeof opts.channel === "string");
|
||||
assignIf(payload, "to", opts.to, typeof opts.to === "string");
|
||||
assignIf(
|
||||
payload,
|
||||
"bestEffortDeliver",
|
||||
opts.bestEffortDeliver,
|
||||
typeof opts.bestEffortDeliver === "boolean",
|
||||
);
|
||||
patch.payload = payload;
|
||||
}
|
||||
|
||||
if (typeof opts.postPrefix === "string") {
|
||||
patch.isolation = {
|
||||
postToMainPrefix: opts.postPrefix.trim() ? opts.postPrefix : "Cron",
|
||||
};
|
||||
if (hasDeliveryModeFlag || hasDeliveryTarget || hasBestEffort) {
|
||||
const deliveryMode =
|
||||
opts.announce || opts.deliver === true
|
||||
? "announce"
|
||||
: opts.deliver === false
|
||||
? "none"
|
||||
: "announce";
|
||||
const delivery: Record<string, unknown> = { mode: deliveryMode };
|
||||
if (typeof opts.channel === "string") {
|
||||
const channel = opts.channel.trim();
|
||||
delivery.channel = channel ? channel : undefined;
|
||||
}
|
||||
if (typeof opts.to === "string") {
|
||||
const to = opts.to.trim();
|
||||
delivery.to = to ? to : undefined;
|
||||
}
|
||||
if (typeof opts.bestEffortDeliver === "boolean") {
|
||||
delivery.bestEffort = opts.bestEffortDeliver;
|
||||
}
|
||||
patch.delivery = delivery;
|
||||
}
|
||||
|
||||
const res = await callGatewayFromCli("cron.update", opts, {
|
||||
|
||||
@@ -60,18 +60,18 @@ export function parseDurationMs(input: string): number | null {
|
||||
return Math.floor(n * factor);
|
||||
}
|
||||
|
||||
export function parseAtMs(input: string): number | null {
|
||||
export function parseAt(input: string): string | null {
|
||||
const raw = input.trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const absolute = parseAbsoluteTimeMs(raw);
|
||||
if (absolute) {
|
||||
return absolute;
|
||||
if (absolute !== null) {
|
||||
return new Date(absolute).toISOString();
|
||||
}
|
||||
const dur = parseDurationMs(raw);
|
||||
if (dur) {
|
||||
return Date.now() + dur;
|
||||
if (dur !== null) {
|
||||
return new Date(Date.now() + dur).toISOString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -97,13 +97,14 @@ const truncate = (value: string, width: number) => {
|
||||
return `${value.slice(0, width - 3)}...`;
|
||||
};
|
||||
|
||||
const formatIsoMinute = (ms: number) => {
|
||||
const d = new Date(ms);
|
||||
const formatIsoMinute = (iso: string) => {
|
||||
const parsed = parseAbsoluteTimeMs(iso);
|
||||
const d = new Date(parsed ?? NaN);
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
return "-";
|
||||
}
|
||||
const iso = d.toISOString();
|
||||
return `${iso.slice(0, 10)} ${iso.slice(11, 16)}Z`;
|
||||
const isoStr = d.toISOString();
|
||||
return `${isoStr.slice(0, 10)} ${isoStr.slice(11, 16)}Z`;
|
||||
};
|
||||
|
||||
const formatDuration = (ms: number) => {
|
||||
@@ -143,7 +144,7 @@ const formatRelative = (ms: number | null | undefined, nowMs: number) => {
|
||||
|
||||
const formatSchedule = (schedule: CronSchedule) => {
|
||||
if (schedule.kind === "at") {
|
||||
return `at ${formatIsoMinute(schedule.atMs)}`;
|
||||
return `at ${formatIsoMinute(schedule.at)}`;
|
||||
}
|
||||
if (schedule.kind === "every") {
|
||||
return `every ${formatDuration(schedule.everyMs)}`;
|
||||
|
||||
@@ -15,7 +15,7 @@ export function addGatewayClientOptions(cmd: Command) {
|
||||
return cmd
|
||||
.option("--url <url>", "Gateway WebSocket URL (defaults to gateway.remote.url when configured)")
|
||||
.option("--token <token>", "Gateway token (if required)")
|
||||
.option("--timeout <ms>", "Timeout in ms", "10000")
|
||||
.option("--timeout <ms>", "Timeout in ms", "30000")
|
||||
.option("--expect-final", "Wait for final response (agent)", false);
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,12 @@ describe("cli program (smoke)", () => {
|
||||
key: "sk-moonshot-test",
|
||||
field: "moonshotApiKey",
|
||||
},
|
||||
{
|
||||
authChoice: "moonshot-api-key-cn",
|
||||
flag: "--moonshot-api-key",
|
||||
key: "sk-moonshot-cn-test",
|
||||
field: "moonshotApiKey",
|
||||
},
|
||||
{
|
||||
authChoice: "kimi-code-api-key",
|
||||
flag: "--kimi-code-api-key",
|
||||
|
||||
@@ -58,7 +58,7 @@ export function registerOnboardCommand(program: Command) {
|
||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||
.option(
|
||||
"--auth-choice <choice>",
|
||||
"Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|xiaomi-api-key|qianfan-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip",
|
||||
"Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|cloudflare-ai-gateway-api-key|moonshot-api-key|moonshot-api-key-cn|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|xiaomi-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip|qianfan-api-key",
|
||||
)
|
||||
.option(
|
||||
"--token-provider <id>",
|
||||
@@ -74,6 +74,9 @@ export function registerOnboardCommand(program: Command) {
|
||||
.option("--openai-api-key <key>", "OpenAI API key")
|
||||
.option("--openrouter-api-key <key>", "OpenRouter API key")
|
||||
.option("--ai-gateway-api-key <key>", "Vercel AI Gateway API key")
|
||||
.option("--cloudflare-ai-gateway-account-id <id>", "Cloudflare Account ID")
|
||||
.option("--cloudflare-ai-gateway-gateway-id <id>", "Cloudflare AI Gateway ID")
|
||||
.option("--cloudflare-ai-gateway-api-key <key>", "Cloudflare AI Gateway API key")
|
||||
.option("--moonshot-api-key <key>", "Moonshot API key")
|
||||
.option("--kimi-code-api-key <key>", "Kimi Coding API key")
|
||||
.option("--gemini-api-key <key>", "Gemini API key")
|
||||
@@ -126,6 +129,9 @@ export function registerOnboardCommand(program: Command) {
|
||||
openaiApiKey: opts.openaiApiKey as string | undefined,
|
||||
openrouterApiKey: opts.openrouterApiKey as string | undefined,
|
||||
aiGatewayApiKey: opts.aiGatewayApiKey as string | undefined,
|
||||
cloudflareAiGatewayAccountId: opts.cloudflareAiGatewayAccountId as string | undefined,
|
||||
cloudflareAiGatewayGatewayId: opts.cloudflareAiGatewayGatewayId as string | undefined,
|
||||
cloudflareAiGatewayApiKey: opts.cloudflareAiGatewayApiKey as string | undefined,
|
||||
moonshotApiKey: opts.moonshotApiKey as string | undefined,
|
||||
kimiCodeApiKey: opts.kimiCodeApiKey as string | undefined,
|
||||
geminiApiKey: opts.geminiApiKey as string | undefined,
|
||||
|
||||
Reference in New Issue
Block a user