mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-07 07:55:47 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Scheduled routines provide recurring control-plane work without manual intervention > - The new activity gate can suppress scheduled runs when no external work occurred > - The core scheduler and database support landed without a public create/update contract > - Agents, operators, and managed plugins need validated fields plus discoverable semantics to opt in safely > - This pull request exposes the activity gate through routine APIs, revisions, plugin contracts, tests, and skill documentation > - The benefit is backward-compatible control over idle scheduled work without losing activity-triggered follow-up ## Linked Issues or Issue Description - Refs #8534 ## What Changed - Added shared activity-gate policy and scope enums with create/PATCH validation. - Persisted activity-gate fields through routine creation, updates, revision snapshots, pipeline snapshots, and revision restores. - Defaulted legacy revision snapshots during restore and added regression coverage for pre-field snapshots. - Extended managed-plugin routine declarations, production reconciliation, and the SDK test harness to preserve non-default gate settings. - Added end-to-end API coverage for create/PATCH/list/detail round-trips, defaults, and invalid enum rejection. - Documented schedule-only semantics, activity windows, own-run/read-action exclusions, scopes, and an hourly quiet-night watcher example. ## Verification - `pnpm exec vitest run packages/shared/src/validators/routine.test.ts server/src/__tests__/routines-service.test.ts server/src/__tests__/routines-e2e.test.ts` - `pnpm exec vitest run packages/shared/src/validators/plugin.test.ts packages/plugins/sdk/tests/testing-actions.test.ts server/src/__tests__/plugin-managed-routines.test.ts server/src/__tests__/routines-service.test.ts -t 'activity gate|preserves declared activity gate settings|resolves routine agent and project refs'` - `pnpm exec vitest run ui/src/lib/workspace-routines.test.ts ui/src/pages/Routines.test.tsx` - `pnpm --filter @paperclipai/shared typecheck` - `pnpm --filter @paperclipai/plugin-sdk typecheck` - `pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm check:token-gates` - GitHub CI: all final-head checks green; Storybook visual regression skipped by path rules. - Greptile: 5/5 with no unresolved review threads. ## Risks - Low risk: defaults remain `always` and `company`, preserving existing routine behavior and old revision snapshots. - Managed plugin manifests can now declare the same validated gate settings as the public routine API; omitted values retain core defaults. - Revision snapshots now include the new fields so policy changes are not lost or treated as no-ops during restore. > For core feature work, checked `ROADMAP.md`: this extends the existing Scheduled Routines roadmap item and does not duplicate a separate planned capability. ## Model Used - OpenAI GPT-5.5 via Codex CLI, with repository tool use and code execution; context-window size was not exposed by the runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import type { RoutineListItem } from "@paperclipai/shared";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
getWorkspaceSpecificRoutineVariableNames,
|
|
routineHasWorkspaceSpecificVariables,
|
|
sortWorkspaceRoutinesByName,
|
|
} from "./workspace-routines";
|
|
|
|
function createRoutine(overrides: Partial<RoutineListItem> = {}): RoutineListItem {
|
|
return {
|
|
id: "routine-1",
|
|
companyId: "company-1",
|
|
projectId: "project-1",
|
|
goalId: null,
|
|
parentIssueId: null,
|
|
responsibleUserId: null,
|
|
title: "Routine title",
|
|
description: null,
|
|
assigneeAgentId: "agent-1",
|
|
priority: "medium",
|
|
status: "active",
|
|
concurrencyPolicy: "coalesce_if_active",
|
|
catchUpPolicy: "skip_missed",
|
|
activityGatePolicy: "always",
|
|
activityGateScope: "company",
|
|
variables: [],
|
|
latestRevisionId: null,
|
|
latestRevisionNumber: 1,
|
|
createdByAgentId: null,
|
|
createdByUserId: null,
|
|
updatedByAgentId: null,
|
|
updatedByUserId: null,
|
|
lastTriggeredAt: null,
|
|
lastEnqueuedAt: null,
|
|
createdAt: new Date("2026-04-30T00:00:00.000Z"),
|
|
updatedAt: new Date("2026-04-30T00:00:00.000Z"),
|
|
triggers: [],
|
|
lastRun: null,
|
|
activeIssue: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("workspace routine helpers", () => {
|
|
it("matches routines with explicit workspace variables", () => {
|
|
const routine = createRoutine({
|
|
variables: [
|
|
{ name: "workspaceBranch", label: null, type: "text", defaultValue: null, required: true, options: [] },
|
|
],
|
|
});
|
|
|
|
expect(routineHasWorkspaceSpecificVariables(routine)).toBe(true);
|
|
expect(getWorkspaceSpecificRoutineVariableNames(routine)).toEqual(["workspaceBranch"]);
|
|
});
|
|
|
|
it("matches routines that reference workspace variables in templates", () => {
|
|
const routine = createRoutine({
|
|
title: "Review {{ workspaceBranch }}",
|
|
description: "Check branch {{workspaceBranch}}",
|
|
});
|
|
|
|
expect(getWorkspaceSpecificRoutineVariableNames(routine)).toEqual(["workspaceBranch"]);
|
|
});
|
|
|
|
it("ignores routines with only non-workspace variables", () => {
|
|
const routine = createRoutine({
|
|
title: "Review {{repo}}",
|
|
variables: [
|
|
{ name: "repo", label: null, type: "text", defaultValue: null, required: true, options: [] },
|
|
],
|
|
});
|
|
|
|
expect(routineHasWorkspaceSpecificVariables(routine)).toBe(false);
|
|
});
|
|
|
|
it("sorts workspace routines by name regardless of update order", () => {
|
|
const routines = [
|
|
createRoutine({
|
|
id: "routine-2",
|
|
title: "zeta review",
|
|
updatedAt: new Date("2026-05-02T00:00:00.000Z"),
|
|
}),
|
|
createRoutine({
|
|
id: "routine-3",
|
|
title: "Alpha review",
|
|
updatedAt: new Date("2026-04-30T00:00:00.000Z"),
|
|
}),
|
|
createRoutine({
|
|
id: "routine-1",
|
|
title: "alpha review",
|
|
updatedAt: new Date("2026-05-03T00:00:00.000Z"),
|
|
}),
|
|
];
|
|
|
|
expect(sortWorkspaceRoutinesByName(routines).map((routine) => routine.id)).toEqual([
|
|
"routine-1",
|
|
"routine-3",
|
|
"routine-2",
|
|
]);
|
|
expect(routines.map((routine) => routine.id)).toEqual(["routine-2", "routine-3", "routine-1"]);
|
|
});
|
|
});
|