Make workspace, agent, and schedule automation consistent (#2186)
* feat(workspaces): align agent and schedule automation Make workspace identity the shared placement boundary for MCP and CLI, while caller identity determines parentage. Keep heartbeats minimal and cron-based without changing legacy rolling interval semantics. * test(cli): expect canonical schedule cadence * fix(automation): preserve workspace and schedule compatibility * fix(automation): preserve compatibility edges * fix(workspaces): align MCP lifecycle resolution * fix(workspaces): preserve creation intent * fix(workspaces): preserve branch and schedule identity
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { parseScheduleCreateInput, parseScheduleUpdateInput } from "./shared.js";
|
||||
import {
|
||||
compileEveryPresetToCron,
|
||||
parseScheduleCreateInput,
|
||||
parseScheduleUpdateInput,
|
||||
} from "./shared.js";
|
||||
|
||||
const baseOptions = {
|
||||
prompt: "do the thing",
|
||||
@@ -68,13 +72,9 @@ describe("parseScheduleCreateInput cwd/host validation", () => {
|
||||
});
|
||||
|
||||
describe("parseScheduleCreateInput first-run timing", () => {
|
||||
test("--every with no run-now flag fires immediately on creation", () => {
|
||||
test("--every compiles to cron and waits for the next slot", () => {
|
||||
const input = parseScheduleCreateInput(baseOptions);
|
||||
expect(input.runOnCreate).toBe(true);
|
||||
});
|
||||
|
||||
test("--every with --no-run-now waits the interval", () => {
|
||||
const input = parseScheduleCreateInput({ ...baseOptions, runNow: false });
|
||||
expect(input.cadence).toEqual({ type: "cron", expression: "*/5 * * * *" });
|
||||
expect(input.runOnCreate).toBe(false);
|
||||
});
|
||||
|
||||
@@ -101,22 +101,9 @@ describe("parseScheduleCreateInput first-run timing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("--every with --run-now is rejected as redundant", () => {
|
||||
expect(() => parseScheduleCreateInput({ ...baseOptions, runNow: true })).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "REDUNDANT_RUN_NOW",
|
||||
message: expect.stringContaining("--run-now is redundant with --every"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test("--cron with --no-run-now is rejected as redundant", () => {
|
||||
expect(() => parseScheduleCreateInput({ ...baseCron, runNow: false })).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "REDUNDANT_NO_RUN_NOW",
|
||||
message: expect.stringContaining("--no-run-now is redundant with --cron"),
|
||||
}),
|
||||
);
|
||||
test("--run-now is orthogonal to a compiled preset", () => {
|
||||
const input = parseScheduleCreateInput({ ...baseOptions, runNow: true });
|
||||
expect(input.runOnCreate).toBe(true);
|
||||
});
|
||||
|
||||
test("--timezone without --cron is rejected", () => {
|
||||
@@ -159,10 +146,10 @@ describe("parseScheduleUpdateInput", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("parses --every cadence", () => {
|
||||
test("compiles --every cadence to cron", () => {
|
||||
expect(parseScheduleUpdateInput({ id: "abc", every: "5m" })).toEqual({
|
||||
id: "abc",
|
||||
cadence: { type: "every", everyMs: 5 * 60_000 },
|
||||
cadence: { type: "cron", expression: "*/5 * * * *" },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -271,3 +258,25 @@ describe("parseScheduleUpdateInput", () => {
|
||||
).toThrow(expect.objectContaining({ code: "CONFLICTING_EXPIRES" }));
|
||||
});
|
||||
});
|
||||
|
||||
describe("compileEveryPresetToCron", () => {
|
||||
test.each([
|
||||
["1m", "*/1 * * * *"],
|
||||
["15m", "*/15 * * * *"],
|
||||
["1h", "0 * * * *"],
|
||||
["6h", "0 */6 * * *"],
|
||||
["1d", "0 0 * * *"],
|
||||
])("compiles %s", (value, expected) => {
|
||||
expect(compileEveryPresetToCron(value)).toBe(expected);
|
||||
});
|
||||
|
||||
test.each(["30s", "7m", "5h", "2d"])("rejects rolling interval %s", (value) => {
|
||||
expect(() => compileEveryPresetToCron(value)).toThrow(
|
||||
expect.objectContaining({ code: "UNREPRESENTABLE_CADENCE" }),
|
||||
);
|
||||
});
|
||||
|
||||
test.each(["15minutes", "junk15m", "1h-nope"])("rejects malformed preset %s", (value) => {
|
||||
expect(() => compileEveryPresetToCron(value)).toThrow("Invalid duration format");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user