This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 36332e267539 CAMEL-24861: camel_run starts the directory as the app 
with --source-dir, and camel_control gets a reload action
36332e267539 is described below

commit 36332e267539d90713d151f917c1a10210a7b8cc
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 12:06:02 2026 +0200

    CAMEL-24861: camel_run starts the directory as the app with --source-dir, 
and camel_control gets a reload action
    
    ## Description
    
    Two gaps met while an agent built an integration step by step through the 
camel-jbang-mcp server:
    
    - **`camel_run` started with the files of the moment.** It listed the 
directory's source files and passed them to `camel run`, so a file the agent 
added afterwards, a `beans.yaml`, a Java class under `src/main/java`, was not 
part of the app, and `restart` replayed the same list. With no files named, the 
tool now starts `camel run --source-dir=.` in the directory: the directory is 
watched, a changed or added file is reloaded in dev mode (with #26653, a 
directory created later as well), [...]
    - **`camel_control` had no reload.** It has stop, kill, restart, 
stop-routes, start-routes and reset-stats; the CLI has `camel cmd reload`, 
which writes the `reload` action the CLI connector hands to the reload 
strategy. A reload loads the routes again from their files without a restart: a 
changed stylesheet or a dropped data file takes effect, and a file consumed 
once is read again. The tool gets the same action, answered like reset-stats 
(the runtime writes no result for it; `camel_ [...]
    
    Live check through the tool code: `camel_run` on a directory started `camel 
run --source-dir=. --dev`, the `reload` action returned, `stop` stopped it; and 
with `camel cmd reload` on the same app, a file consumed once was read again 
after the reload. Tool descriptions, the MCP page's tool table and the upgrade 
guide updated.
    
    ## Tests
    
    `IntegrationLauncherTest.noFilesRunsTheDirectoryAsTheApp`: the 
`--source-dir=.` argument line with and without a name and extra arguments; the 
existing tests for the explicit file list unchanged. Launcher, authoring tools 
and tool registry tests (33) green, including the prompt budget test with the 
longer descriptions.
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  5 +++
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  7 +++-
 .../dsl/jbang/core/commands/ai/AuthoringTools.java | 13 +++---
 .../core/commands/ai/IntegrationLauncher.java      | 46 +++++++++++++++++++---
 .../core/commands/ai/IntegrationLauncherTest.java  |  9 +++++
 .../core/commands/tui/AiPanelPromptBudgetTest.java |  4 +-
 6 files changed, 69 insertions(+), 15 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 3068b1a2da1f..111908c48536 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -851,6 +851,11 @@ eight seconds for the reload of the written file and 
answers with its outcome (`
 with the cause and the validator's report, properties, or unknown), so an 
agent does not go on with a route that
 did not load. A write with no selected integration answers as before.
 
+The `camel_run` tool, when no files are named, starts the project with `camel 
run --source-dir=.` instead of
+listing the directory's files: the directory is watched, so a file added 
afterwards (a bean file, a Java class
+under `src/main/java`) is part of the app and reloaded in dev mode, and a 
`restart` starts the same way. Naming
+files keeps the previous behaviour. The `camel_control` tool gets a `reload` 
action, what `camel cmd reload` does.
+
 In the TUI MCP server the tools `tui_catalog_doc`, `tui_validate_source`, 
`tui_write_file`, `tui_get_files`,
 `tui_control`, `tui_get_log`, `tui_get_errors` and `tui_eval_expression` were 
renamed to the `camel_` names
 above; the `tui_` prefix is now reserved for the tools that only make sense in 
front of the screen. Update
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index 30f7ee2a02e2..fb83a312dd51 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -339,11 +339,14 @@ project `directory` as an argument, the runtime tools 
take the integration `name
   `read-only` access level of the security layer hides the tool altogether.
 
 | `camel_run`
-| Starts an integration from a project directory with `camel run` in a 
separate process, in dev mode by
-  default so route files reload when written. Returns the pid, name and log 
file once the integration is up.
+| Starts an integration from a project directory with `camel run --source-dir` 
in a separate process, in dev
+  mode by default: the directory is watched, so a changed or added file (a 
route, a bean file, a Java class) is
+  reloaded. Name files to run only those, for a directory that holds several 
apps. Returns the pid, name and
+  log file once the integration is up.
 
 | `camel_control`
 | Controls a running integration: `stop`, `kill`, `restart` (picks up edited 
files without dev mode),
+  `reload` (loads the routes again from their files without a restart, as 
`camel cmd reload` does),
   `stop-routes`, `start-routes`, `reset-stats`.
 
 | `camel_get_log`
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
index 684cc67872a2..04b622cc58f7 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
@@ -218,10 +218,9 @@ public final class AuthoringTools {
                 }));
 
         registry.accept(tool("camel_run",
-                "Starts an integration with camel run in a separate process, 
in dev mode by default (files reload when written). Returns the pid and log 
file; camel_get_log, camel_get_errors and camel_control follow it.")
+                "Starts an integration with camel run in a separate process, 
in dev mode by default (a changed or added file is reloaded). Returns the pid 
and log file; camel_get_log and camel_control follow it.")
                 .param("directory", "string", "Project directory to run in", 
true)
-                .param("files", "string", "Source files to run, 
comma-separated (default: every route file in the"
-                                          + " directory)",
+                .param("files", "string", "Source files to run, 
comma-separated (default: the whole directory)",
                         false)
                 .param("name", "string", "Integration name (default: from the 
first file)", false)
                 .param("dev", "boolean", "Dev mode with reload on file change 
(default true)", false)
@@ -247,9 +246,11 @@ public final class AuthoringTools {
 
         registry.accept(tool("camel_control",
                 "Controls a running integration: stop (graceful), kill, 
restart (picks up edited files without dev "
-                                              + "mode), stop-routes, 
start-routes, reset-stats (clears statistics, routes "
-                                              + "untouched). Never stop, kill 
or restart unless the user asked for it.")
-                .param("action", "string", "stop, kill, restart, stop-routes, 
start-routes or reset-stats", true)
+                                              + "mode), reload (loads the 
routes again without a restart, e.g. after a changed "
+                                              + "stylesheet), stop-routes, 
start-routes, reset-stats (clears statistics). "
+                                              + "Never stop, kill or restart 
unless the "
+                                              + "user asked for it.")
+                .param("action", "string", "stop, kill, restart, reload, 
stop-routes, start-routes or reset-stats", true)
                 .param("name", "string", NAME_DESC, false)
                 .readOnly(false)
                 .destructive(true)
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
index 09409e7738b6..7d64a6322d95 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
@@ -56,10 +56,15 @@ public final class IntegrationLauncher {
      */
     public static JsonObject run(Path directory, List<String> files, String 
name, boolean dev, List<String> extraArgs) {
         List<String> cmd = new ArrayList<>(LauncherHelper.getCamelCommand());
-        List<String> sources = files == null || files.isEmpty() ? 
sourceFiles(directory) : files;
-        cmd.addAll(runArguments(sources, name, dev, extraArgs));
+        // no files given: the whole directory is the app (camel run 
--source-dir), so a file the agent adds later,
+        // a bean file, a Java class under src/main/java, is part of it and 
reloaded in dev mode (CAMEL-24861);
+        // with files given only those run, for a directory that holds several 
apps
+        boolean sourceDir = files == null || files.isEmpty();
+        List<String> sources = sourceDir ? sourceFiles(directory) : files;
+        cmd.addAll(sourceDir ? sourceDirArguments(name, dev, extraArgs) : 
runArguments(sources, name, dev, extraArgs));
         JsonObject result = new JsonObject();
-        if (sources.isEmpty()) {
+        // with --source-dir the guard looks at the top level only; a class 
under src/main/java is an app too
+        if (!sourceDir && sources.isEmpty()) {
             result.put("directory", directory.toString());
             result.put("status", "failed");
             result.put("error", "No source files to run in " + directory
@@ -105,9 +110,13 @@ public final class IntegrationLauncher {
                 result.put("name", started);
                 result.put("log", LogFileReader.logFile(pid, 
info.name()).toString());
                 result.put("devMode", dev);
+                result.put("sourceDir", sourceDir);
                 result.put("message", "Started " + started + " (pid " + pid + 
")"
                                       + (dev
-                                              ? "; dev mode reloads the routes 
when a source file changes"
+                                              ? sourceDir
+                                                      ? "; dev mode watches 
the directory: a changed or added file is"
+                                                        + " reloaded"
+                                                      : "; dev mode reloads 
the routes when a source file changes"
                                               : "; restart it after changing a 
source file")
                                       + ". camel_get_log reads its log, 
camel_get_errors its failed exchanges.");
                 return result;
@@ -156,6 +165,24 @@ public final class IntegrationLauncher {
         return names;
     }
 
+    /** The {@code camel run --source-dir=.} arguments: the directory the 
process starts in is the app. */
+    static List<String> sourceDirArguments(String name, boolean dev, 
List<String> extraArgs) {
+        List<String> cmd = new ArrayList<>();
+        cmd.add("run");
+        cmd.add("--source-dir=.");
+        if (dev) {
+            cmd.add("--dev");
+        }
+        if (name != null && !name.isBlank()) {
+            cmd.add("--name=" + name);
+        }
+        cmd.add("--logging-color=false");
+        if (extraArgs != null) {
+            cmd.addAll(extraArgs);
+        }
+        return cmd;
+    }
+
     /**
      * The {@code camel run} arguments for the given files, name and mode.
      *
@@ -204,7 +231,7 @@ public final class IntegrationLauncher {
      * Controls a running integration.
      *
      * @param  ctx    the context with the selected process
-     * @param  action stop (graceful), kill, restart, stop-routes, 
start-routes or reset-stats
+     * @param  action stop (graceful), kill, restart, reload, stop-routes, 
start-routes or reset-stats
      * @return        what was done
      */
     public static String control(ToolContext ctx, String action) {
@@ -233,8 +260,15 @@ public final class IntegrationLauncher {
                 ctx.executeAction("reset-stats", null);
                 yield "Statistics reset for pid " + pid;
             }
+            case "reload" -> {
+                // what camel cmd reload does: the routes are loaded again 
from their files without a restart, so
+                // a changed stylesheet or a dropped data file takes effect, 
and a file consumed once is read again
+                ctx.executeAction("reload", null);
+                yield "Reload triggered for pid " + pid + "; camel_get_log 
shows the routes reloaded summary";
+            }
             default -> throw new ToolExecutionException(
-                    "Unknown action: " + action + ". Use stop, kill, restart, 
stop-routes, start-routes or reset-stats");
+                    "Unknown action: " + action + ". Use stop, kill, restart, 
reload, stop-routes, start-routes or"
+                                                        + " reset-stats");
         };
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
index 46dcd3fa9f24..5d5fca25d53e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
@@ -45,6 +45,15 @@ class IntegrationLauncherTest {
         
assertThat(IntegrationLauncher.sourceFiles(dir.resolve("nope"))).isEmpty();
     }
 
+    /** CAMEL-24861: with no files given the directory is the app, so files 
added later are part of it. */
+    @Test
+    void noFilesRunsTheDirectoryAsTheApp() {
+        assertThat(IntegrationLauncher.sourceDirArguments(null, true, null))
+                .containsExactly("run", "--source-dir=.", "--dev", 
"--logging-color=false");
+        assertThat(IntegrationLauncher.sourceDirArguments("demo", false, 
List.of("--port=9000")))
+                .containsExactly("run", "--source-dir=.", "--name=demo", 
"--logging-color=false", "--port=9000");
+    }
+
     @Test
     void filesNameAndExtraArgumentsArePassedThrough() {
         assertThat(IntegrationLauncher.runArguments(List.of("a.camel.yaml", 
"application.properties"), "demo", true,
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
index 2bfd77cc224f..b3ceac02cc9e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
@@ -48,7 +48,9 @@ class AiPanelPromptBudgetTest {
     // and from 8500 when camel_catalog_doc gained the api kind (CAMEL-24708): 
its kind argument names the core
     // classes and script languages the API reference covers, which is what 
makes a model ask for them
     // raised with the core budget (CAMEL-24760)
-    static final int FULL_BUDGET_TOKENS = 9_200;
+    // raised from 9200 when camel_control gained the reload action 
(CAMEL-24861): main was ~9180 already; the
+    // camel_run and camel_control descriptions were shortened in the same 
change
+    static final int FULL_BUDGET_TOKENS = 9_300;
 
     record Prefix(String mode, int tools, long promptChars, long toolChars) {
 

Reply via email to