feat(workflows): Quick Workflows — @slug omnibar, management UI, cron scheduling#106
Conversation
✅ Registry ValidationTest Coverage: 97/136 features have
|
Go Benchmarks (Tier 1) |
E2E RPC Latency |
Frontend Terminal Throughput |
UX Analysis
|
🎬 E2E Feature Demos2 shard(s) recorded feature flows for this PR. recordings shard 1 Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a “Quick Workflows” feature across the Stapler Squad stack: workflows are persisted in SQLite (ent), exposed via new SessionService RPCs, invokable via @slug in the omnibar, manageable via a new /workflows UI, and optionally schedulable via cron (robfig/cron).
Changes:
- Adds workflow persistence (ent schema + repository) and 5 new workflow RPCs on
SessionService(CRUD + RunWorkflow). - Implements a cron-backed
WorkflowSchedulerplus server wiring to start/stop it and hot-reload entries on workflow changes. - Adds frontend management UI (
/workflows) and omnibar detection/dispatch plumbing for@slug [arg].
Reviewed changes
Copilot reviewed 58 out of 63 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| web-app/src/lib/routes.ts | Adds /workflows route constant. |
| web-app/src/lib/omnibar/types.ts | Adds InputType.Workflow metadata for omnibar. |
| web-app/src/lib/omnibar/detectors/WorkflowDetector.ts | New detector for @slug [arg] parsing. |
| web-app/src/lib/omnibar/detectors/WorkflowDetector.test.ts | Unit tests for WorkflowDetector behavior. |
| web-app/src/lib/omnibar/detector.ts | Adds registry unregister() + resetDefaultRegistry() for tests. |
| web-app/src/lib/omnibar/actions/types.ts | Extends OmnibarAction union with run_workflow. |
| web-app/src/lib/omnibar/actions/dispatch.ts | Dispatch support + analytics for run_workflow. |
| web-app/src/lib/omnibar/actions/dispatch.test.ts | Tests for run_workflow dispatch behavior. |
| web-app/src/lib/nav-pages.ts | Adds “Workflows” nav entry. |
| web-app/src/lib/hooks/useWorkflows.ts | New ConnectRPC hook to CRUD workflows. |
| web-app/src/lib/hooks/useSessionService.ts | Adds runWorkflow RPC helper to the session service hook. |
| web-app/src/lib/contexts/OmnibarContext.tsx | Dynamically registers WorkflowDetector + wires omnibar run handler. |
| web-app/src/components/workflows/WorkflowsPanel.tsx | New workflows management panel (CRUD UI). |
| web-app/src/components/workflows/WorkflowsPanel.css.ts | Styles for workflows panel and modal overlay. |
| web-app/src/components/workflows/WorkflowForm.tsx | Create/edit workflow form UI. |
| web-app/src/components/workflows/WorkflowForm.css.ts | Styles for the workflow form. |
| web-app/src/components/sessions/Omnibar.tsx | Adds onRunWorkflow handling for workflow detections. |
| web-app/src/app/workflows/page.tsx | New /workflows page entrypoint. |
| web-app/src/app/workflows/page.css.ts | Page layout styling for /workflows. |
| web-app/src/app/workflows/layout.tsx | Next.js metadata/layout wrapper for /workflows. |
| session/workflow_slug.go | Adds backend slug validation helper. |
| session/workflow_slug_test.go | Tests for workflow slug validation. |
| session/workflow_repository.go | Defines workflow repository interface + input structs. |
| session/session_driver.go | Removes trailing whitespace (no functional change). |
| session/instance_claude.go | Removes trailing whitespace (no functional change). |
| session/ent/schema/workflow.go | Adds ent Workflow schema. |
| session/ent/schema/approvalrule.go | Makes several JSON fields nullable to ease SQLite migration. |
| session/ent/runtime.go | Registers generated runtime hooks/validators for Workflow. |
| session/ent/migrate/schema.go | Adds workflows table schema + nullable JSON columns. |
| session/ent/hook/hook.go | Adds ent hook adapter type for Workflow mutations. |
| session/ent/approvalrule/where.go | Adds IsNil/NotNil predicates for nullable JSON columns. |
| session/ent/approvalrule_update.go | Adds Clear* helpers + SQL clear handling for nullable JSON fields. |
| session/ent/approvalrule_create.go | Removes “missing required field” checks for now-optional JSON fields; adds Clear* upsert helpers. |
| session/ent_workflow_repository.go | Implements workflow persistence via ent. |
| server/workflows/scheduler.go | Implements cron scheduler + FireNow + validation helper. |
| server/services/workflow_service.go | Implements workflow RPCs (CRUD + RunWorkflow). |
| server/services/workflow_service_test.go | Adds CRUD-focused workflow service tests. |
| server/services/session_service.go | Adds workflow subservice injection + delegates RPC methods. |
| server/server.go | Starts workflow scheduler during server wiring. |
| server/dependencies.go | Wires WorkflowRepo + WorkflowScheduler into dependency graph. |
| scripts/install-service.sh | Adds health check + auto-rollback behavior after install. |
| proto/session/v1/session.proto | Adds workflow RPCs + messages to SessionService proto. |
| project_plans/quick-workflows/research/ui-patterns.md | Research notes for UI patterns/navigation. |
| project_plans/quick-workflows/research/stack-storage.md | Research notes for workflow persistence options. |
| project_plans/quick-workflows/research/omnibar-architecture.md | Research notes for omnibar detector/action architecture. |
| project_plans/quick-workflows/research/cron-scheduling.md | Research notes for cron scheduler approach. |
| project_plans/quick-workflows/requirements.md | Requirements document for the feature. |
| project_plans/quick-workflows/implementation/validation.md | Validation/test planning document. |
| Makefile | Adds backup/rollback targets and integrates install-service backup behavior. |
| go.sum | Adds robfig/cron checksum entries. |
| go.mod | Adds robfig/cron dependency. |
| gen/proto/go/session/v1/sessionv1connect/session.connect.go | Generated Connect client/server glue for new workflow RPCs. |
| docs/registry/features/workflows-management.json | Adds frontend registry entry for workflows UI. |
| docs/registry/features/workflow-detector.json | Adds frontend registry entry for workflow detector. |
| docs/registry/features/backend/UpdateWorkflow.json | Adds backend registry entry for UpdateWorkflow RPC. |
| docs/registry/features/backend/RunWorkflow.json | Adds backend registry entry for RunWorkflow RPC. |
| docs/registry/features/backend/ListWorkflows.json | Adds backend registry entry for ListWorkflows RPC. |
| docs/registry/features/backend/DeleteWorkflow.json | Adds backend registry entry for DeleteWorkflow RPC. |
| docs/registry/features/backend/CreateWorkflow.json | Adds backend registry entry for CreateWorkflow RPC. |
Files not reviewed (4)
- gen/proto/go/session/v1/session.pb.go: Generated file
- gen/proto/go/session/v1/sessionv1connect/session.connect.go: Generated file
- session/ent/approvalrule/where.go: Generated file
- session/ent/approvalrule_create.go: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| prompt := wf.InputTemplate | ||
| if prompt != "" && arg != "" { | ||
| prompt = strings.ReplaceAll(prompt, "{{input}}", arg) | ||
| } else if prompt == "" { | ||
| prompt = arg | ||
| } |
| wfCopy := wf // capture for closure | ||
| entryID, err := s.c.AddFunc(wf.CronExpression, func() { | ||
| ctx := context.Background() | ||
| if _, fireErr := s.FireNow(ctx, wfCopy, ""); fireErr != nil { | ||
| log.Error("[WorkflowScheduler] cron job failed", "slug", wfCopy.Slug, "err", fireErr) | ||
| } |
| // Validate required fields per ADR-9. | ||
| if req.Msg.Command == "" { | ||
| return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("command is required")) | ||
| } | ||
| if req.Msg.TargetDirectory == "" { | ||
| return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("target_directory is required")) | ||
| } | ||
| // Validate cron expression if provided. | ||
| if req.Msg.CronExpression != "" { | ||
| if err := workflows.ValidateCronExpression(req.Msg.CronExpression); err != nil { | ||
| return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid cron expression: %w", err)) | ||
| } | ||
| } |
| const deleteWorkflow = useCallback( | ||
| async (id: string) => { | ||
| if (!clientRef.current) return; | ||
| // Optimistic update. | ||
| setWorkflows((prev) => prev.filter((w) => w.id !== id)); | ||
| const req = create(DeleteWorkflowRequestSchema, { id }); | ||
| await clientRef.current.deleteWorkflow(req); | ||
| }, |
| required | ||
| disabled={isEdit} | ||
| pattern="[a-z0-9][a-z0-9-]*[a-z0-9]" | ||
| title="2-64 lowercase chars, hyphens allowed, no leading/trailing hyphens" | ||
| /> |
| /** | ||
| * WorkflowDetector detects @slug [arg] syntax for quick workflow invocation. | ||
| * Priority 25 — after GitHub URL detectors (10/20/30) and before NewSessionDetector (35). | ||
| */ | ||
|
|
| require ( | ||
| github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect | ||
| github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect | ||
| github.com/robfig/cron/v3 v3.0.1 // indirect | ||
| ) |
| // CreateWorkflow delegates to WorkflowService. | ||
| func (s *SessionService) CreateWorkflow(ctx context.Context, req *connect.Request[sessionv1.CreateWorkflowRequest]) (*connect.Response[sessionv1.CreateWorkflowResponse], error) { | ||
| if s.workflowSvc == nil { | ||
| return nil, connect.NewError(connect.CodeUnavailable, fmt.Errorf("workflow service not available")) | ||
| } |
| // Start WorkflowScheduler (nil guard: disabled when workflow repo is unavailable). | ||
| if deps.WorkflowScheduler != nil { | ||
| deps.WorkflowScheduler.Start(serverCtx) | ||
| srv.shutdownHooks = append(srv.shutdownHooks, deps.WorkflowScheduler.Stop) | ||
| log.Info("WorkflowScheduler started") | ||
| } |
✅ Registry ValidationTest Coverage: 97/136 features have
|
…ent UI, cron scheduling Introduces a flexible workflow definition system that lets users save named configurations (skill/command, target directory, input template, session type, model) and invoke them instantly from the omnibar, a dedicated management panel, or on a cron schedule. Key additions: - `@slug [arg]` omnibar syntax (WorkflowDetector, priority 25, collision-free) - `/workflows` management page: create/edit/delete workflows via WorkflowForm + WorkflowsPanel - WorkflowScheduler (robfig/cron/v3) fires one-off sessions on schedule; hot-reloads on CRUD - 5 new proto RPCs: CreateWorkflow, UpdateWorkflow, DeleteWorkflow, ListWorkflows, RunWorkflow - ent ORM Workflow entity (auto-migrated on startup, slug UNIQUE index) - Circular import avoided: WorkflowSchedulerInterface in services/, SessionServiceInterface in workflows/, deferred SetWorkflowService injection - 64 new tests (Go unit/integration + Jest + slug validation) - Missed-fire policy: skipped cron runs during downtime are not backfilled (v1 non-goal) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BLOCKER/CRITICAL: - Log Reload errors instead of discarding with _ (B1) - Use errors.Is(err, session.ErrNotFound) instead of ent.IsNotFound after repo wrapping (B2) - Add 8-second Stop() timeout to prevent hung server shutdown (C2) - Rollback optimistic delete on RPC failure in useWorkflows (C3) - Reject cron_enabled=true with empty cron_expression at validation layer (C4) - Add ErrConflict sentinel; ent_workflow_repository converts ConstraintError (D3/D5) - Validate target_directory is absolute and free of traversal components (S1/S2) MAJOR: - Add 5-minute timeout to cron callback context (M1) - Add SessionTypeOneOff constant; scheduler uses it instead of magic string (M4) - Replace window.confirm with inline confirmDeleteId state in WorkflowsPanel (M5) - Add Limit(1000) safety cap to ListAll (A4) Tests: - mockScheduler struct; TestCreateWorkflow_WithCronEnabled_CallsReload (T1) - TestRunWorkflow_HappyPath + TestRunWorkflow_NotFound (T2) - Fix delegation test: create workflow first, assert Len==1 (T3) - Add multi-field UpdateWorkflow test (T4) - Add 64-char and 65-char slug boundary cases (T5) - Add default registry @-input fallthrough test (T6) - Add analytics.track assertion for run_workflow dispatch (T7) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s, registry format - FireNow: include wf.Command in InitialPrompt (was silently dropped; sessions launched without the core skill/command entirely) - CreateWorkflow: add Name == "" validation for explicit CodeInvalidArgument - scheduler.Start: remove duplicate Stop() goroutine (server.go already registers Stop as a shutdown hook, creating a double-stop risk) - session_service.go: add // +api: markers to all 5 workflow delegation methods - WorkflowDetector.ts: add // +feature: marker for registry scanner - registry JSON: fix filePath→path + add markerLine to match established schema - WorkflowForm.tsx: tighten slug pattern ([a-z0-9]+(-[a-z0-9]+)*) + add minLength/maxLength to reject consecutive hyphens and enforce 2–64 length - go.mod: go mod tidy to mark robfig/cron/v3 as direct dependency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
d2ff3d7 to
02cb76f
Compare
✅ Registry ValidationTest Coverage: 97/136 features have
|
Summary
@slug [optional-arg], from a dedicated management panel, or on a cron scheduleWorkflowent entity in SQLite — auto-migrated on startup, no manual SQL neededWorkflowDetectorat priority 25 uses@slug [arg]syntax — verified collision-free against all 9 existing detectors;run_workflowOmnibarAction dispatches viaRunWorkflowRPCWorkflowSchedulerwrapsrobfig/cron/v3, hot-reloads on CRUD changes, fires one-off sessions; circular import avoided viaWorkflowSchedulerInterface+SessionServiceInterface+ deferredSetWorkflowServiceinjectionSessionService: CreateWorkflow, UpdateWorkflow, DeleteWorkflow, ListWorkflows, RunWorkflowUsage
Architecture notes
directory/one_offexisting paths (7-touchpoint registry not required)targetDirectoryvalidated as required at RPC handler layer; Optional in ent schema for future flexibilityTest plan
npx tsc --noEmit)workflow_slug_test.gotests/e2e/workflows.spec.tsrequires live server — run withmake install-service+cd tests/e2e && npm test🤖 Generated with Claude Code