diff --git a/packages/app/src/app/app.tsx b/packages/app/src/app/app.tsx
index 52e85afb..cb6aff60 100644
--- a/packages/app/src/app/app.tsx
+++ b/packages/app/src/app/app.tsx
@@ -2695,6 +2695,8 @@ export default function App() {
return openworkServerStatus() === "connected" && Boolean(client && workspaceId);
});
+ const schedulerPluginInstalled = createMemo(() => isPluginInstalledByName("opencode-scheduler"));
+
const refreshScheduledJobs = async (options?: { force?: boolean }) => {
if (scheduledJobsBusy() && !options?.force) return;
@@ -2742,6 +2744,12 @@ export default function App() {
return;
}
+ if (!schedulerPluginInstalled()) {
+ setScheduledJobs([]);
+ setScheduledJobsStatus(null);
+ return;
+ }
+
setScheduledJobsBusy(true);
setScheduledJobsStatus(null);
@@ -4801,6 +4809,7 @@ export default function App() {
scheduledJobs: scheduledJobs(),
scheduledJobsSource: scheduledJobsSource(),
scheduledJobsSourceReady: scheduledJobsSourceReady(),
+ schedulerPluginInstalled: schedulerPluginInstalled(),
scheduledJobsStatus: scheduledJobsStatus(),
scheduledJobsBusy: scheduledJobsBusy(),
scheduledJobsUpdatedAt: scheduledJobsUpdatedAt(),
diff --git a/packages/app/src/app/pages/dashboard.tsx b/packages/app/src/app/pages/dashboard.tsx
index 42289600..c8823df0 100644
--- a/packages/app/src/app/pages/dashboard.tsx
+++ b/packages/app/src/app/pages/dashboard.tsx
@@ -138,6 +138,7 @@ export type DashboardViewProps = {
scheduledJobs: ScheduledJob[];
scheduledJobsSource: "local" | "remote";
scheduledJobsSourceReady: boolean;
+ schedulerPluginInstalled: boolean;
scheduledJobsStatus: string | null;
scheduledJobsBusy: boolean;
scheduledJobsUpdatedAt: number | null;
@@ -1289,6 +1290,12 @@ export default function DashboardView(props: DashboardViewProps) {
createSessionAndOpen={props.createSessionAndOpen}
setPrompt={props.setPrompt}
newTaskDisabled={props.newTaskDisabled}
+ schedulerInstalled={props.schedulerPluginInstalled}
+ canEditPlugins={props.canEditPlugins}
+ addPlugin={props.addPlugin}
+ reloadWorkspaceEngine={props.reloadWorkspaceEngine}
+ reloadBusy={props.reloadBusy}
+ canReloadWorkspace={props.canReloadWorkspace}
/>
diff --git a/packages/app/src/app/pages/scheduled.tsx b/packages/app/src/app/pages/scheduled.tsx
index b2d9b272..a507ea01 100644
--- a/packages/app/src/app/pages/scheduled.tsx
+++ b/packages/app/src/app/pages/scheduled.tsx
@@ -14,6 +14,7 @@ import {
MessageSquare,
Plus,
Play,
+ PlugZap,
RefreshCw,
Terminal,
Trash2,
@@ -36,6 +37,12 @@ export type ScheduledTasksViewProps = {
createSessionAndOpen: () => void;
setPrompt: (value: string) => void;
newTaskDisabled: boolean;
+ schedulerInstalled: boolean;
+ canEditPlugins: boolean;
+ addPlugin: (pluginNameOverride?: string) => void;
+ reloadWorkspaceEngine: () => Promise;
+ reloadBusy: boolean;
+ canReloadWorkspace: boolean;
};
const toRelative = (value?: string | null) => {
@@ -351,9 +358,9 @@ const AutomationJobCard = (props: {
{
if (props.source === "remote") return props.sourceReady;
- return isTauriRuntime() && !props.isWindows;
+ return (
+ isTauriRuntime() &&
+ !props.isWindows &&
+ props.schedulerInstalled &&
+ !schedulerInstallRequested()
+ );
});
+ const schedulerGateActive = createMemo(() => {
+ if (props.source !== "local") return false;
+ if (!isTauriRuntime() || props.isWindows) return false;
+ return !props.schedulerInstalled || schedulerInstallRequested();
+ });
+ const schedulerGateMode = createMemo(() => (props.schedulerInstalled ? "reload" : "install"));
+ const automationDisabled = createMemo(() => props.newTaskDisabled || schedulerGateActive());
const supportNote = createMemo(() => {
if (props.source === "remote") {
return props.sourceReady ? null : "OpenWork server unavailable. Connect to sync scheduled tasks.";
}
if (!isTauriRuntime()) return "Scheduled tasks require the desktop app.";
if (props.isWindows) return "Scheduler is not supported on Windows yet.";
+ if (!props.schedulerInstalled || schedulerInstallRequested()) return null;
return null;
});
const sourceDescription = createMemo(() =>
@@ -519,7 +541,19 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
platform.openLink("https://github.com/anomalyco/opencode-scheduler");
};
+ const handleInstallScheduler = async () => {
+ if (installingScheduler() || !props.canEditPlugins) return;
+ setInstallingScheduler(true);
+ setSchedulerInstallRequested(true);
+ try {
+ await Promise.resolve(props.addPlugin("opencode-scheduler"));
+ } finally {
+ setInstallingScheduler(false);
+ }
+ };
+
const openCreateModal = () => {
+ if (automationDisabled()) return;
const root = props.activeWorkspaceRoot.trim();
if (!automationProject().trim() && root) {
setAutomationProject(root);
@@ -528,6 +562,7 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
};
const openCreateModalFromTemplate = (template: (typeof automationTemplates)[number]) => {
+ if (automationDisabled()) return;
const root = props.activeWorkspaceRoot.trim();
if (root) {
setAutomationProject(root);
@@ -545,6 +580,7 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
};
const handleCreateAutomation = () => {
+ if (automationDisabled()) return;
const promptValue = createPromptValue();
if (!promptValue) return;
props.setPrompt(promptValue);
@@ -625,9 +661,9 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
{sourceDescription()}
+
+
+
+
+
+
+
+ {schedulerGateMode() === "reload"
+ ? "Reload OpenWork to activate automations"
+ : "Install the scheduler to unlock automations"}
+
+
+ {schedulerGateMode() === "reload"
+ ? "OpenCode loads plugins at startup. Reload OpenWork to activate opencode-scheduler."
+ : "Automations run through the opencode-scheduler plugin. Add it to this workspace to enable scheduling."}
+
+
+
+
+
+ {installingScheduler() ? "Installing..." : "Install scheduler"}
+
+ void props.reloadWorkspaceEngine()}
+ disabled={!props.canReloadWorkspace || props.reloadBusy || !props.schedulerInstalled}
+ >
+ {props.reloadBusy ? "Reloading..." : "Reload OpenWork"}
+
+
+ View docs
+
+
+
+
+
+
{supportNote()}
@@ -671,7 +754,7 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
0}
fallback={
-
+
No automations yet. Pick a template or create your own automation prompt.
@@ -683,7 +766,7 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
description={card.description}
tone={card.tone}
onClick={() => openCreateModalFromTemplate(card)}
- disabled={props.newTaskDisabled}
+ disabled={automationDisabled()}
/>
)}
@@ -698,7 +781,7 @@ export default function ScheduledTasksView(props: ScheduledTasksViewProps) {
}
>
-
+
{(job) => (
- Create
+ type="button"
+ onClick={handleCreateAutomation}
+ disabled={!canCreateAutomation() || automationDisabled()}
+ class={`px-4 py-2 text-xs font-medium rounded-lg transition-colors ${
+ !canCreateAutomation() || automationDisabled()
+ ? "bg-gray-3 text-gray-8 cursor-not-allowed"
+ : "bg-gray-12 text-gray-1 hover:bg-gray-11"
+ }`}
+ >
+ Create