Skip to content

[codex] Add workflow extension plugin seams#1498

Merged
gsxdsm merged 3 commits into
mainfrom
feature/engine-plugin
Jun 8, 2026
Merged

[codex] Add workflow extension plugin seams#1498
gsxdsm merged 3 commits into
mainfrom
feature/engine-plugin

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a generic workflow extension plugin layer so optional workflow behavior can be registered through plugins instead of hardcoded into the shared engine.

Changes

  • Adds workflow extension contribution types, manifest validation, registry, and PluginRunner registration lifecycle.
  • Preserves plugin-namespaced workflow IR metadata on columns and nodes with schema-aware validation.
  • Adds engine seams for move policies, column work-engine dispatch, workflow node handlers, task verdict providers, and auto-merge fact providers.
  • Adds shared board action services for canonical move/update operations.
  • Exports the extension contract through the plugin SDK and documents the authoring model.
  • Adds a changeset for the published package.

Validation

  • pnpm --filter @fusion/core exec vitest run src/__tests__/plugin-contribution-types.test.ts src/__tests__/workflow-extension-registry.test.ts src/__tests__/plugin-loader-contributions.test.ts src/__tests__/workflow-ir-extension-metadata.test.ts src/__tests__/transition-parity.test.ts src/__tests__/board-action-services.test.ts --silent=passed-only --reporter=dot
  • pnpm --filter @fusion/engine exec vitest run src/__tests__/plugin-runner.test.ts src/__tests__/workflow-work-engine-dispatch.test.ts src/__tests__/workflow-node-handler-extensions.test.ts src/__tests__/workflow-verdict-provider-extensions.test.ts src/__tests__/auto-merge-fact-providers.test.ts --silent=passed-only --reporter=dot
  • pnpm --filter @fusion/core typecheck
  • pnpm --filter @fusion/engine typecheck
  • pnpm --filter @fusion/plugin-sdk typecheck
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Plugins can register workflow extensions (move policies, work engines, node handlers, verdict providers, auto-merge fact providers) that can veto or claim actions, gate completion, and influence auto-merge routing.
    • Board action services for programmatic task moves and updates.
    • Workflows accept plugin-contributed extension metadata with configurable fallback/degrade behaviors.
  • Documentation

    • Plugin authoring guide and glossary updated with workflow-extension authoring, schema, and fallback guidance.
  • Tests

    • Expanded test coverage validating extension registration, registry lifecycle, IR metadata parsing, and runtime behaviors.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a1b3102d-d750-44a8-8679-b1081f370505

📥 Commits

Reviewing files that changed from the base of the PR and between 01523a6 and 6d47d1a.

📒 Files selected for processing (2)
  • packages/core/src/store.ts
  • packages/engine/src/__tests__/workflow-node-handler-extensions.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/engine/src/tests/workflow-node-handler-extensions.test.ts
  • packages/core/src/store.ts

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive workflow extension plugin system that allows plugins to register six kinds of extensions (move policies, work engines, node handlers, verdict providers, auto-merge facts, and shared board action services) via a central registry. Extensions integrate at multiple execution checkpoints: store move validation, task dispatch, workflow graph execution, verdict evaluation, and auto-merge routing.

Changes

Workflow Extension Plugin System

Layer / File(s) Summary
Extension Type Contracts & Registry Foundation
packages/core/src/workflow-extension-types.ts, packages/core/src/workflow-extension-registry.ts
Defines discriminated extension kinds, handler/input/output types, registry id helper, and registry operations including upsert and expanded degradation reasons.
Plugin Contribution Types & Loader Aggregation
packages/core/src/plugin-types.ts, packages/core/src/plugin-loader.ts
Adds workflowExtensions discovery and contribution shapes, implements validateWorkflowExtensionContribution, extends validatePluginManifest, and adds PluginLoader.getPluginWorkflowExtensions().
Workflow IR Extension Metadata & Validation
packages/core/src/workflow-ir-types.ts, packages/core/src/workflow-ir.ts
Adds extensions to IR nodes/columns, validates plugin-namespaced extension keys and metadata against registered config schemas, and prevents v2→v1 downgrade when extensions are present.
Board Action Services
packages/core/src/board-action-services.ts
Introduces BoardActionTaskStore and createBoardActionServices wrapper that forwards moveTask/updateTask to the store (defaulting source to "user").
Store Move Policy Evaluation
packages/core/src/store.ts
Extends MoveTaskOptions with workflow move fields, adds move-policy preflight evaluation that runs before acquiring per-task lock, invokes registered move-policy extensions with timeout/fallback handling, and rejects or degrades moves per decisions.
Plugin Runner Lifecycle & Adapter
packages/engine/src/plugin-runner.ts, packages/engine/src/plugin-workflow-extension-adapter.ts
Adds cached workflow extension contributions with versioning, getPluginWorkflowExtensions(), syncPluginWorkflowExtensions(), disablePluginWorkflowExtensions(), and adapter helpers to register/unregister/degrade registry entries; wires cache invalidation into plugin lifecycle events.
Task Execution: Work-Engine Dispatch & Verdict Providers
packages/engine/src/executor.ts
Adds maybeDispatchWorkflowWorkEngine() to dispatch to work-engine extensions early (claim/park behavior) and evaluateTaskVerdictProviders() to gate fn_task_done; both integrated with logging and run-audit events.
Auto-Merge Routing Integration
packages/engine/src/auto-merge-fact-providers.ts, packages/engine/src/merger.ts
Implements evaluateAutoMergeFactProviders to collect facts and choose the strictest route; integrates into merger to compute initial merge-request state and records provider telemetry in audit events.
Workflow Graph Node-Handler Extensions
packages/engine/src/workflow-graph-executor.ts
Dispatches to node-handler extensions before built-in handlers, normalizes plugin outcomes, applies context patches, and degrades on faults per fallback.
Core & Plugin-SDK Index Exports
packages/core/src/index.ts, packages/plugin-sdk/src/index.ts
Re-exports workflow extension types, constants, registry helpers, validators, and board-action services for plugin authors.
Comprehensive Tests
packages/core/src/__tests__/*, packages/engine/src/__tests__/*
Adds/updates tests covering registry, contribution validation, loader aggregation, IR metadata parsing, move-policy parity, node-handler/work-engine/verdict-provider/auto-merge integrations, board-action services, and plugin-runner cache/sync behavior.
Docs & Release Notes
.changeset/workflow-extension-plugins.md, CONCEPTS.md, docs/PLUGIN_AUTHORING.md, docs/plans/2026-06-08-001-feat-workflow-extension-plugins-plan.md
Adds changeset, glossary entry, plugin authoring guide section (16.6) describing contribution format/kinds/fallbacks/IR rules, and a detailed design plan (U1–U10).

Sequence Diagrams

sequenceDiagram
  participant Plugin as Plugin Registry
  participant Runner as Plugin Runner
  participant Registry as Extension Registry
  Plugin->>Runner: register/load plugin
  Runner->>Runner: call getPluginWorkflowExtensions()
  Runner->>Runner: syncPluginWorkflowExtensions()
  loop for each extension contribution
    Runner->>Registry: register(pluginId, extension)
    Registry-->>Runner: WorkflowExtensionDefinition
  end
  Runner->>Runner: track registered IDs per plugin
  Plugin->>Runner: disable/unload plugin
  Runner->>Runner: call disablePluginWorkflowExtensions(pluginId)
  alt force=true
    Runner->>Registry: degrade(ids, "force-disabled", message)
  else
    Runner->>Registry: unregister(ids)
  end
Loading
sequenceDiagram
  participant Client as Client/Agent
  participant Store as TaskStore
  participant Registry as Extension Registry
  participant Policy as Move-Policy Extension
  Client->>Store: moveTask(task, column, {workflowMoveActor, source})
  Store->>Registry: list("move-policy")
  loop for each extension
    Store->>Policy: evaluate({task, columns, actor, source, metadata})
    alt allowed
      Policy-->>Store: {allowed: true}
    else denied
      Policy-->>Store: {allowed: false, reason, message}
      Store-->>Client: TransitionRejectionError
    end
  end
  Store-->>Client: moved task (if all allow)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Runfusion/Fusion#1418: Overlaps in workflow move pipeline changes and intersects store/IR validation surfaces relevant to workflow columns and move preflight.

Poem

🐰 I hopped into the registry, bright and keen,

Plugins bring tricks unseen,
Policies, engines, nodes that play,
Verdicts and facts to guide the way.
Hop on — workflows made more green!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] Add workflow extension plugin seams' is clear and specific, accurately reflecting the main feature introduced: a plugin-extensible workflow system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/engine-plugin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gsxdsm
gsxdsm marked this pull request as ready for review June 8, 2026 03:50
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a generic workflow extension plugin layer, letting optional behaviors (move policies, work engines, node handlers, verdict providers, auto-merge fact providers) be registered by plugins rather than hardcoded in the engine. The preflight+lock split for move-policy evaluation, the upsert-based syncPluginWorkflowExtensions that preserves degraded state, and the schema-aware IR metadata validation are all well-designed.

  • Adds WorkflowExtensionRegistry, contribution types, manifest validation, and full PluginRunner lifecycle management (register, upsert, degrade, unregister).
  • Adds engine seams in executor.ts, workflow-graph-executor.ts, merger.ts, and store.ts, each dispatching to registered extensions with fallback/degrade semantics.
  • Exports the entire extension contract through @fusion/plugin-sdk and updates authoring docs.

Confidence Score: 4/5

Safe to merge with fixes to the degradeToDefault fault-handling gaps in three engine handlers.

Three out of five extension fault handlers — evaluateAutoMergeFactProviders, evaluateTaskVerdictProviders, and maybeDispatchWorkflowWorkEngine — skip the registry.degrade() call when fallback is degradeToDefault. Without the fix, a plugin extension that throws repeatedly will keep running on every auto-merge, every fn_task_done, and every heartbeat dispatch cycle instead of being permanently disabled after the first fault.

packages/engine/src/auto-merge-fact-providers.ts and packages/engine/src/executor.ts (both evaluateTaskVerdictProviders and maybeDispatchWorkflowWorkEngine).

Important Files Changed

Filename Overview
packages/core/src/store.ts Adds workflow move-policy evaluation via a two-phase preflight+lock design. bypassGuards skip logic is correctly applied. Stale-preflight check inside the lock is sound.
packages/engine/src/auto-merge-fact-providers.ts New evaluateAutoMergeFactProviders correctly aggregates facts and chooses the strictest route, but degradeToDefault fault handling omits the required registry.degrade() call, so faulting providers are never permanently disabled.
packages/engine/src/executor.ts Adds work-engine dispatch and verdict-provider seams. Both new fault handlers use continue without registry.degrade() for degradeToDefault, leaving the extension active after a fault.
packages/engine/src/workflow-graph-executor.ts Plugin node-handler seam correctly degrades the extension on degradeToDefault fault and falls through to built-in handlers.
packages/core/src/workflow-extension-registry.ts New registry with upsert semantics that preserves the degraded state on cache-invalidation syncs, correctly addressing the previously reported regression.
packages/engine/src/plugin-runner.ts Lifecycle management for workflow extensions mirrors the trait pattern; syncPluginWorkflowExtensions now uses upsert so force-degraded entries survive cache invalidation.
packages/core/src/workflow-extension-types.ts New contribution type hierarchy is well-structured and matches the six extension kinds. Handler signatures are sound.
packages/engine/src/merger.ts Auto-merge fact provider evaluation is correctly gated behind isMergeRequestContractShadowEnabled and handles global settings.autoMerge vs task-level opt-in.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant TaskStore
    participant Registry as WorkflowExtensionRegistry
    participant MovePolicy as Plugin: MovePolicy
    participant Executor
    participant WorkEngine as Plugin: WorkEngine
    participant VerdictProvider as Plugin: VerdictProvider
    participant Merger
    participant FactProvider as Plugin: FactProvider

    Caller->>TaskStore: moveTask(id, toColumn)
    TaskStore->>TaskStore: prepareWorkflowMovePolicyPreflight() [outside lock]
    TaskStore->>Registry: list(move-policy)
    Registry-->>TaskStore: [policies]
    TaskStore->>MovePolicy: evaluate(input) [5s timeout]
    MovePolicy-->>TaskStore: allowed true/false
    TaskStore->>TaskStore: "withTaskLock -> moveTaskInternal()"
    TaskStore->>TaskStore: validate stale-preflight check
    TaskStore-->>Caller: Task
    Executor->>Executor: execute(task)
    Executor->>Registry: get(extensionId) per column.extensions
    Executor->>WorkEngine: dispatch(input)
    WorkEngine-->>Executor: claimed / not-claimed / parked
    Executor->>VerdictProvider: evaluate(input) at fn_task_done
    VerdictProvider-->>Executor: status pass/fail/blocked
    Merger->>FactProvider: collect(input)
    FactProvider-->>Merger: route, facts, reason
    Merger->>Merger: chooseStricterAutoMergeRoute()
    Merger->>Merger: upsertMergeRequestRecord(state)
Loading

Reviews (4): Last reviewed commit: "fix(engine): tighten workflow extension ..." | Re-trigger Greptile

Comment thread packages/engine/src/plugin-runner.ts
Comment thread packages/engine/src/plugin-runner.ts
Comment thread .changeset/workflow-extension-plugins.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/engine/src/merger.ts (1)

8154-8175: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor global settings.autoMerge before queueing or running the shadow merge request.

This still auto-queues/runs when project-level auto-merge is off. If settings.autoMerge === false and the task does not explicitly opt back in with autoMerge: true, initialState becomes "queued" and the next block promotes it to "running", which breaks the terminal-until-human-merge contract.

Suggested fix
-    const providerManualRoute =
-      autoMergeFacts.route === "manual-required" ||
-      autoMergeFacts.route === "blocked";
-    const initialState = task.autoMerge === false || providerManualRoute ? "manual-required" : "queued";
+    const providerManualRoute =
+      autoMergeFacts.route === "manual-required" ||
+      autoMergeFacts.route === "blocked";
+    const autoMergeManuallyGated =
+      task.autoMerge === false ||
+      (settings.autoMerge === false && task.autoMerge !== true) ||
+      providerManualRoute;
+    const initialState = autoMergeManuallyGated ? "manual-required" : "queued";
@@
-    if (task.autoMerge !== false) {
+    if (!autoMergeManuallyGated) {
       if (currentState === "retrying") {
         store.transitionMergeRequestState(task.id, "queued");
         store.transitionMergeRequestState(task.id, "running");
       } else if (currentState === "queued") {
         store.transitionMergeRequestState(task.id, "running");
       }
     }

As per coding guidelines: When settings.autoMerge: false, in-review is terminal-until-merged by a human. Based on learnings: this short-circuit is intentionally skipped only when the task has an explicit per-task autoMerge: true override.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/src/merger.ts` around lines 8154 - 8175, The code queues/runs
shadow merges even when global settings.autoMerge is false because initialState
and state transitions only consider task.autoMerge and providerManualRoute;
update the logic to honor settings.autoMerge by treating the merge as
manual-required unless the task explicitly opts in (task.autoMerge === true):
change the initialState calculation to set "manual-required" when
settings.autoMerge === false AND task.autoMerge !== true (in addition to
providerManualRoute or task.autoMerge === false), and guard the transition block
(the branch that calls store.transitionMergeRequestState to "queued"/"running")
to skip/promote only when settings.autoMerge !== false OR task.autoMerge ===
true so shadow merges are not queued/run when global autoMerge is disabled.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/workflow-extension-plugins.md:
- Line 2: The changeset currently sets "`@runfusion/fusion`" to "patch" but this
PR adds a new feature surface, so edit the changeset entry that mentions
"`@runfusion/fusion`" and change its version bump from "patch" to "minor" (update
the value on the line containing "`@runfusion/fusion`": patch →
"`@runfusion/fusion`": minor) so the release type matches the repo policy for new
features.

In `@packages/core/src/__tests__/workflow-extension-registry.test.ts`:
- Around line 8-16: The test helper function extension() currently hardcodes
kind: "move-policy" causing mismatches when called with other IDs (e.g.,
extension("work-engine")); change extension() to accept a kind parameter (with a
sensible default) and set the returned object's kind to that parameter (not the
hardcoded string), and then update call sites (such as the call at
extension("work-engine")) to pass the correct kind where needed so extensionId
and kind remain consistent.

In `@packages/core/src/store.ts`:
- Around line 6529-6537: The call to evaluateWorkflowMovePolicies is currently
invoked for every flagged move and can block hard-cancel and recovery rehome
moves; modify the call site so that before invoking evaluateWorkflowMovePolicies
(the call shown with task, workflowIr, fromColumn, toColumn, actor from
resolveWorkflowMoveActor, and source from options?.workflowMoveSource), you
detect and bypass policy evaluation for hard-cancel moves and recovery rehome
moves — specifically: treat a user-initiated in-progress→todo move identified as
a hard-cancel (use the same detection logic used for moveTask/user pause
semantics or the actor resolved by resolveWorkflowMoveActor) and any move whose
source equals the recoveryRehome source/flag (options?.workflowMoveSource or
moveSource) as exemptions; only call evaluateWorkflowMovePolicies when the move
is not one of these exempt cases so hard cancels and recovery rehomes are not
vetoed by plugins.
- Around line 6349-6358: The evaluateWorkflowMovePolicies call is running while
the task lock is held (moveTask -> withTaskLock), which blocks other mutations
if a plugin hangs; move the plugin execution out of the lock and add a
degradation/timeout boundary like the plugin-gate path does. Specifically,
change the flow so evaluateWorkflowMovePolicies (and any extension.evaluate
calls inside it) are invoked outside withTaskLock (or refactor
evaluateWorkflowMovePolicies to schedule/await extension.evaluate via the same
plugin-gate helper used elsewhere), enforce a hard timeout and mark the
definition.degraded on timeout/exception, and keep only non-plugin state updates
inside withTaskLock to avoid blocking other task mutations. Ensure you reference
evaluateWorkflowMovePolicies, moveTask, withTaskLock, extension.evaluate, and
getWorkflowExtensionRegistry when making the change.

In `@packages/core/src/workflow-ir.ts`:
- Around line 976-988: The enum branch in validate logic (check where field.type
=== "enum") currently accepts any string if field.enumValues is undefined;
change it to require field.enumValues to be a non-empty array: if
field.enumValues is missing or has length 0, throw a WorkflowIrError indicating
that the extension (use owner and key and field.key in the message) must declare
enumValues for enum fields, then continue to validate that the provided value
(enumValue) is included in field.enumValues as before.

In `@packages/engine/src/plugin-runner.ts`:
- Around line 578-584: The collectPluginWorkflowExtensionIds method builds
registry IDs manually; import and use the workflowExtensionRegistryId helper
from `@fusion/core` instead: add the import for workflowExtensionRegistryId and
replace the template string `plugin:${pluginId}:${entry.extension.extensionId}`
inside collectPluginWorkflowExtensionIds with a call to
workflowExtensionRegistryId(pluginId, entry.extension.extensionId) so ID
generation matches the adapter and central helper.

In `@packages/engine/src/workflow-graph-executor.ts`:
- Around line 527-528: The catch block checking extension.fallback ===
"degradeToDefault" currently just continues to the default handler but never
marks the extension as degraded; update that catch clause in
workflow-graph-executor.ts to invoke the registry's degradation API for the
failing extension (e.g., call nodeHandlerRegistry.degrade(extension.id) or the
equivalent degradeExtension/degrade method available in this module) before
falling through to the default handler, handle any errors from the degrade call
gracefully, and then continue to the default path.
- Around line 491-499: The normalizePluginNodeResult function currently discards
a plugin-provided value when normalizing custom outcomes (outcome starting with
"outcome:"); update normalizePluginNodeResult in WorkflowGraphExecutor to
preserve result.value if present (i.e., set value to result.value if defined,
otherwise fall back to result.outcome.slice("outcome:".length)), keep
contextPatch unchanged, and return a WorkflowNodeResult with outcome "success"
for custom outcomes; ensure types align with WorkflowNodeExtensionResult and
WorkflowNodeResult so custom outcome values are not lost.

---

Outside diff comments:
In `@packages/engine/src/merger.ts`:
- Around line 8154-8175: The code queues/runs shadow merges even when global
settings.autoMerge is false because initialState and state transitions only
consider task.autoMerge and providerManualRoute; update the logic to honor
settings.autoMerge by treating the merge as manual-required unless the task
explicitly opts in (task.autoMerge === true): change the initialState
calculation to set "manual-required" when settings.autoMerge === false AND
task.autoMerge !== true (in addition to providerManualRoute or task.autoMerge
=== false), and guard the transition block (the branch that calls
store.transitionMergeRequestState to "queued"/"running") to skip/promote only
when settings.autoMerge !== false OR task.autoMerge === true so shadow merges
are not queued/run when global autoMerge is disabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3962ffc1-f89e-4a1b-a072-f9afdb65ec48

📥 Commits

Reviewing files that changed from the base of the PR and between fa36d68 and 71822f2.

📒 Files selected for processing (31)
  • .changeset/workflow-extension-plugins.md
  • CONCEPTS.md
  • docs/PLUGIN_AUTHORING.md
  • docs/plans/2026-06-08-001-feat-workflow-extension-plugins-plan.md
  • packages/core/src/__tests__/board-action-services.test.ts
  • packages/core/src/__tests__/plugin-contribution-types.test.ts
  • packages/core/src/__tests__/plugin-loader-contributions.test.ts
  • packages/core/src/__tests__/transition-parity.test.ts
  • packages/core/src/__tests__/workflow-extension-registry.test.ts
  • packages/core/src/__tests__/workflow-ir-extension-metadata.test.ts
  • packages/core/src/board-action-services.ts
  • packages/core/src/index.ts
  • packages/core/src/plugin-loader.ts
  • packages/core/src/plugin-types.ts
  • packages/core/src/store.ts
  • packages/core/src/workflow-extension-registry.ts
  • packages/core/src/workflow-extension-types.ts
  • packages/core/src/workflow-ir-types.ts
  • packages/core/src/workflow-ir.ts
  • packages/engine/src/__tests__/auto-merge-fact-providers.test.ts
  • packages/engine/src/__tests__/plugin-runner.test.ts
  • packages/engine/src/__tests__/workflow-node-handler-extensions.test.ts
  • packages/engine/src/__tests__/workflow-verdict-provider-extensions.test.ts
  • packages/engine/src/__tests__/workflow-work-engine-dispatch.test.ts
  • packages/engine/src/auto-merge-fact-providers.ts
  • packages/engine/src/executor.ts
  • packages/engine/src/merger.ts
  • packages/engine/src/plugin-runner.ts
  • packages/engine/src/plugin-workflow-extension-adapter.ts
  • packages/engine/src/workflow-graph-executor.ts
  • packages/plugin-sdk/src/index.ts
👮 Files not reviewed due to content moderation or server errors (1)
  • packages/engine/src/executor.ts

Comment thread .changeset/workflow-extension-plugins.md Outdated
Comment thread packages/core/src/__tests__/workflow-extension-registry.test.ts Outdated
Comment thread packages/core/src/store.ts
Comment thread packages/core/src/store.ts Outdated
Comment thread packages/core/src/workflow-ir.ts
Comment thread packages/engine/src/plugin-runner.ts
Comment thread packages/engine/src/workflow-graph-executor.ts
Comment thread packages/engine/src/workflow-graph-executor.ts Outdated
@gsxdsm

gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

The bypassGuards gap means engine rebounds can be blocked by plugin policies ... The sync-clears-degrade issue means force-disabled plugins silently resurrect on next cache invalidation.

Addressed in 01523a658: move-policy evaluation now runs as a pre-lock preflight and is skipped for guard-bypassed/recovery/hard-cancel moves, with regression coverage for hard cancel and outside-lock execution. Workflow extension sync now upserts existing definitions so force-degraded entries remain degraded across cache invalidation, with plugin-runner regression coverage.

@gsxdsm

gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Honor global settings.autoMerge before queueing or running the shadow merge request.

Addressed in 01523a658: shadow merge-request state now treats settings.autoMerge === false as manual-required unless the task explicitly opts in with autoMerge: true, and the running transition is guarded by the same predicate. Added merger lifecycle tests for global manual mode and task-level opt-in.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/engine/src/merger.ts (1)

8162-8179: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move existing merge-request records back to manual-required when gating flips on.

This only applies initialState when the record does not exist. If a task is already in queued or retrying and settings.autoMerge is later turned off, or a fact provider starts returning manual-required/blocked, the persisted merge-request record stays runnable instead of being re-gated.

💡 Suggested fix
     const currentState = existingRecord?.state ?? initialState;
     if (!existingRecord) {
       store.upsertMergeRequestRecord(task.id, { state: initialState });
+    } else if (autoMergeManuallyGated && currentState !== "manual-required") {
+      store.transitionMergeRequestState(task.id, "manual-required");
     }

     if (!autoMergeManuallyGated) {
       if (currentState === "retrying") {
         store.transitionMergeRequestState(task.id, "queued");

Based on learnings: When settings.autoMerge: false, in-review is terminal-until-merged by a human.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/src/merger.ts` around lines 8162 - 8179, The code only
applies initialState when no record exists, so when autoMergeManuallyGated
becomes true existing merge records (queued/retrying/running) remain runnable;
update logic after computing autoMergeManuallyGated/currentState to, when
autoMergeManuallyGated is true and an existingRecord exists and currentState !==
"manual-required", call store.transitionMergeRequestState(task.id,
"manual-required") (cover queued/retrying/running -> manual-required) instead of
leaving them unchanged; reference autoMergeManuallyGated, initialState,
existingRecord/currentState, store.getMergeRequestRecord,
store.upsertMergeRequestRecord and store.transitionMergeRequestState.

Source: Learnings

🧹 Nitpick comments (1)
packages/engine/src/plugin-workflow-extension-adapter.ts (1)

7-38: 💤 Low value

Consider consistent parameter patterns across adapter functions.

registerPluginWorkflowExtensions uses a structured params object (line 7-11), while unregisterPluginWorkflowExtensions and degradePluginWorkflowExtensions use positional parameters (lines 21-24, 32-35). For a cohesive adapter API, consider using the same parameter style across all three functions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/src/plugin-workflow-extension-adapter.ts` around lines 7 -
38, The three adapter functions use mixed parameter
styles—registerPluginWorkflowExtensions(params: { registry, pluginId,
contributions }) vs positional params in
unregisterPluginWorkflowExtensions(registry, ids) and
degradePluginWorkflowExtensions(registry, ids, message)—so make them consistent
by converting unregisterPluginWorkflowExtensions and
degradePluginWorkflowExtensions to accept a single params object (e.g., {
registry: WorkflowExtensionRegistry; ids: readonly string[]; message?: string })
or conversely change registerPluginWorkflowExtensions to positional args; update
the function bodies to use params.registry/params.ids/params.message, update any
callers to the new call sites, and keep exported names
(registerPluginWorkflowExtensions, unregisterPluginWorkflowExtensions,
degradePluginWorkflowExtensions) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/store.ts`:
- Around line 1221-1224: The preflight result stored in movePolicyPreflight must
be tied to the task's workflow snapshot so it can't be replayed against a
different workflow; update the preflight to capture the workflow
identifier/version/hash (the same snapshot data produced by
selectTaskWorkflowAndReconcile()/updateWorkflowDefinition), and in moveTask()
after acquiring withTaskLock() compare that saved workflow snapshot against the
current task workflow inside moveTaskInternal(); if they differ, discard the
preflight verdict and re-run policy evaluation under the lock (or fail the move
and require the caller to retry). Ensure identifiers referenced are moveTask(),
moveTaskInternal(), movePolicyPreflight, withTaskLock(),
selectTaskWorkflowAndReconcile(), and updateWorkflowDefinition().

In `@packages/engine/src/__tests__/workflow-node-handler-extensions.test.ts`:
- Around line 69-106: The test's extension mock (registered via
getWorkflowExtensionRegistry().register using
workflowExtensionRegistryId("node-plugin","decision")) returns outcome
"outcome:custom-route" but the assertion expects routing to the "human" node;
update the mock handle to return "outcome:needs-human" so the decide node routes
to the human edge, or alternatively change the expected visitedNodeIds in the
expect(...) assertion to ["start","decide","default"]; modify the return value
in the vi.fn().mockResolvedValue(...) for the registered extension or adjust the
expect(result.visitedNodeIds) accordingly to keep outcome and routing
consistent.

---

Outside diff comments:
In `@packages/engine/src/merger.ts`:
- Around line 8162-8179: The code only applies initialState when no record
exists, so when autoMergeManuallyGated becomes true existing merge records
(queued/retrying/running) remain runnable; update logic after computing
autoMergeManuallyGated/currentState to, when autoMergeManuallyGated is true and
an existingRecord exists and currentState !== "manual-required", call
store.transitionMergeRequestState(task.id, "manual-required") (cover
queued/retrying/running -> manual-required) instead of leaving them unchanged;
reference autoMergeManuallyGated, initialState, existingRecord/currentState,
store.getMergeRequestRecord, store.upsertMergeRequestRecord and
store.transitionMergeRequestState.

---

Nitpick comments:
In `@packages/engine/src/plugin-workflow-extension-adapter.ts`:
- Around line 7-38: The three adapter functions use mixed parameter
styles—registerPluginWorkflowExtensions(params: { registry, pluginId,
contributions }) vs positional params in
unregisterPluginWorkflowExtensions(registry, ids) and
degradePluginWorkflowExtensions(registry, ids, message)—so make them consistent
by converting unregisterPluginWorkflowExtensions and
degradePluginWorkflowExtensions to accept a single params object (e.g., {
registry: WorkflowExtensionRegistry; ids: readonly string[]; message?: string })
or conversely change registerPluginWorkflowExtensions to positional args; update
the function bodies to use params.registry/params.ids/params.message, update any
callers to the new call sites, and keep exported names
(registerPluginWorkflowExtensions, unregisterPluginWorkflowExtensions,
degradePluginWorkflowExtensions) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 41762d39-870b-42c2-b85f-2b93b181b14f

📥 Commits

Reviewing files that changed from the base of the PR and between 71822f2 and 01523a6.

📒 Files selected for processing (14)
  • .changeset/workflow-extension-plugins.md
  • packages/core/src/__tests__/transition-parity.test.ts
  • packages/core/src/__tests__/workflow-extension-registry.test.ts
  • packages/core/src/__tests__/workflow-ir-extension-metadata.test.ts
  • packages/core/src/store.ts
  • packages/core/src/workflow-extension-registry.ts
  • packages/core/src/workflow-ir.ts
  • packages/engine/src/__tests__/merger-merge-lifecycle.test.ts
  • packages/engine/src/__tests__/plugin-runner.test.ts
  • packages/engine/src/__tests__/workflow-node-handler-extensions.test.ts
  • packages/engine/src/merger.ts
  • packages/engine/src/plugin-runner.ts
  • packages/engine/src/plugin-workflow-extension-adapter.ts
  • packages/engine/src/workflow-graph-executor.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/workflow-extension-plugins.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/core/src/tests/workflow-extension-registry.test.ts
  • packages/engine/src/plugin-runner.ts
  • packages/engine/src/workflow-graph-executor.ts
  • packages/core/src/workflow-ir.ts

Comment thread packages/core/src/store.ts
@gsxdsm

gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@gsxdsm

gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gsxdsm
gsxdsm merged commit de25485 into main Jun 8, 2026
6 checks passed
@gsxdsm
gsxdsm deleted the feature/engine-plugin branch June 8, 2026 05:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant