feat: add invoke() to DederProjectInternals for dynamic task invocati…#106
Conversation
…on by plugins Add TaskInvokeOutcome case class, CallerType.Plugin enum variant, and invoke(taskName, moduleIds, args) method to DederProjectInternals trait. Implementation in DederProjectInternalsImpl delegates to DederProjectState through a direct reference (wired in ServerMain), using the same execution pipeline as CLI requests (planning, locking, caching, cancellation). Motivation: plugins (primarily web dashboard) can trigger builds dynamically - just pass task name, module IDs, and args like CLI does.
…ult summary - New TaskInvokerApi trait in plugin-api: invoke(taskName, moduleIds, args, onNotification) returns TaskInvokeResult with per-module outcomes + rendered plaintext summary - TaskInvokeResult wraps Seq[TaskInvokeOutcome] + renderedSummary: Option[String] - Passed to plugins via PluginInitParams.taskInvoker - DederProjectInternalsImpl implements TaskInvokerApi - Refactored invoke into shared invokeInternal that handles both the simple DederProjectInternals.invoke and the richer TaskInvokerApi.invoke - Cross-module summary renders using Summarizable + PlainTextWritable typeclass (same output as CLI)
|
Warning Review limit reached
More reviews will be available in 41 minutes and 58 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a dynamic task invocation API for plugins. A new ChangesPlugin Task Invocation API
Sequence DiagramsequenceDiagram
participant Plugin
participant DederProjectInternalsImpl
participant DederProjectState
participant TaskSummarizer
Plugin->>DederProjectInternalsImpl: invoke(taskName, moduleIds, args, onNotification)
DederProjectInternalsImpl->>DederProjectInternalsImpl: validate projectState set
DederProjectInternalsImpl->>DederProjectInternalsImpl: resolve moduleIds (WildcardUtils / all)
DederProjectInternalsImpl->>DederProjectState: executeTasks(requestId, taskName, modules, args, notificationWrapper)
DederProjectState-->>DederProjectInternalsImpl: Seq[TaskExecResult]
DederProjectInternalsImpl->>DederProjectInternalsImpl: map TaskExecResult → TaskInvokeOutcome
DederProjectInternalsImpl->>TaskSummarizer: summarizeValueUnsafe + OutputFormat.render
TaskSummarizer-->>DederProjectInternalsImpl: Option[String] renderedSummary
DederProjectInternalsImpl-->>Plugin: TaskInvokeResult(outcomes, renderedSummary)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
server/src/ba/sake/deder/plugin/PluginLoader.scala (1)
150-150: ⚖️ Poor tradeoffConsider passing
TaskInvokerApias an explicit constructor parameter.The
asInstanceOf[TaskInvokerApi]cast works becauseDederProjectInternalsImplimplements both traits, but it couplesPluginLoaderto that implementation detail. PassingTaskInvokerApias a separate constructor parameter would make the dependency explicit and type-safe.🤖 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 `@server/src/ba/sake/deder/plugin/PluginLoader.scala` at line 150, In the PluginInitParams constructor call within PluginLoader, remove the asInstanceOf[TaskInvokerApi] cast on internals and instead pass TaskInvokerApi as an explicit separate constructor parameter to PluginInitParams. This requires updating the PluginInitParams case class to accept TaskInvokerApi as its own parameter rather than deriving it from internals through a cast, making the dependency explicit and removing the coupling to DederProjectInternalsImpl's implementation details.
🤖 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 `@server/src/ba/sake/deder/DederProjectInternalsImpl.scala`:
- Around line 140-150: The module resolution logic in the wildcard matching
section silently converts both readState and
WildcardUtils.getMatchesOrRecommendations errors into empty sequences, hiding
failures from callers. Instead of returning Seq.empty for both the Left case in
readState and the Left case in WildcardUtils.getMatchesOrRecommendations,
propagate these errors through the function's return type (likely
TaskInvokeResult) so that plugins can receive distinguished failure outcomes
indicating why module resolution failed. This requires changing the return type
from a simple resolved Ids sequence to a Result type that can communicate both
success and failure states to the caller.
---
Nitpick comments:
In `@server/src/ba/sake/deder/plugin/PluginLoader.scala`:
- Line 150: In the PluginInitParams constructor call within PluginLoader, remove
the asInstanceOf[TaskInvokerApi] cast on internals and instead pass
TaskInvokerApi as an explicit separate constructor parameter to
PluginInitParams. This requires updating the PluginInitParams case class to
accept TaskInvokerApi as its own parameter rather than deriving it from
internals through a cast, making the dependency explicit and removing the
coupling to DederProjectInternalsImpl's implementation details.
🪄 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: 128478c3-cd30-4faf-a3fb-c302219dc575
📒 Files selected for processing (5)
deder-common/src/ba/sake/deder/DederProjectInternals.scalaplugin-api/src/ba/sake/deder/DederPluginApi.scalaserver/src/ba/sake/deder/DederProjectInternalsImpl.scalaserver/src/ba/sake/deder/ServerMain.scalaserver/src/ba/sake/deder/plugin/PluginLoader.scala
| // Resolve wildcards: empty moduleIds → all modules | ||
| val resolvedIds = state.readState(useLastGood = false) match { | ||
| case Left(_) => Seq.empty | ||
| case Right(s) => | ||
| val allIds = s.tasksResolver.allModules.map(_.id) | ||
| if moduleIds.isEmpty then allIds | ||
| else WildcardUtils.getMatchesOrRecommendations(allIds, moduleIds) match { | ||
| case Left(_) => Seq.empty | ||
| case Right(ids) => ids | ||
| } | ||
| } |
There was a problem hiding this comment.
Silent failure when module resolution fails.
When readState returns Left (parse/load error) or WildcardUtils.getMatchesOrRecommendations returns Left (no matches), resolvedIds becomes Seq.empty. This silently produces an empty TaskInvokeResult with no indication to the plugin caller that module resolution failed.
Consider propagating the error or returning a distinguished failure outcome so plugins can diagnose misconfigurations.
Possible approach
val resolvedIds = state.readState(useLastGood = false) match {
- case Left(_) => Seq.empty
+ case Left(err) =>
+ return TaskInvokeResult(Seq.empty, Some(s"Failed to read project state: $err"))
case Right(s) =>
val allIds = s.tasksResolver.allModules.map(_.id)
if moduleIds.isEmpty then allIds
else WildcardUtils.getMatchesOrRecommendations(allIds, moduleIds) match {
- case Left(_) => Seq.empty
+ case Left(suggestions) =>
+ val hint = if suggestions.nonEmpty then s" Did you mean: ${suggestions.mkString(", ")}?" else ""
+ return TaskInvokeResult(Seq.empty, Some(s"No modules matched: ${moduleIds.mkString(", ")}.$hint"))
case Right(ids) => ids
}
}🤖 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 `@server/src/ba/sake/deder/DederProjectInternalsImpl.scala` around lines 140 -
150, The module resolution logic in the wildcard matching section silently
converts both readState and WildcardUtils.getMatchesOrRecommendations errors
into empty sequences, hiding failures from callers. Instead of returning
Seq.empty for both the Left case in readState and the Left case in
WildcardUtils.getMatchesOrRecommendations, propagate these errors through the
function's return type (likely TaskInvokeResult) so that plugins can receive
distinguished failure outcomes indicating why module resolution failed. This
requires changing the return type from a simple resolved Ids sequence to a
Result type that can communicate both success and failure states to the caller.
…ctly to TaskInvokerApi version
…ep only TaskInvokerApi.invoke
…on by plugins
Add TaskInvokeOutcome case class, CallerType.Plugin enum variant, and invoke(taskName, moduleIds, args) method to DederProjectInternals trait. Implementation in DederProjectInternalsImpl delegates to DederProjectState through a direct reference (wired in ServerMain), using the same execution pipeline as CLI requests (planning, locking, caching, cancellation).
Motivation: plugins (primarily web dashboard) can trigger builds dynamically
Summary by CodeRabbit