From 13a89e4d560d7eac6ed7af5057d513878b5a6780 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Mon, 21 Sep 2026 09:38:41 +0200 Subject: [PATCH 1/2] CAMEL-24861: camel_run starts the directory as the app with --source-dir, so files added later are part of it, and camel_control gets a reload action Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj --- .../pages/camel-4x-upgrade-guide-4_23.adoc | 5 +++ .../modules/ROOT/pages/camel-jbang-mcp.adoc | 7 ++- .../core/commands/ai/AuthoringTools.java | 15 ++++--- .../core/commands/ai/IntegrationLauncher.java | 43 ++++++++++++++++--- .../commands/ai/IntegrationLauncherTest.java | 9 ++++ 5 files changed, 66 insertions(+), 13 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 4bef77ec50044..f87f6e10f7717 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 @@ -819,6 +819,11 @@ same `camel_` names by both MCP servers, `camel mcp` and `camel tui --mcp`: `cam `camel_catalog_find`, `camel_validate_source`, `camel_get_files`, `camel_write_file`, `camel_run`, `camel_control`, `camel_get_log`, `camel_get_errors`, `camel_eval_expression` and `camel_error_diagnose`. +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 ec63967b8187e..ea6157a430bd7 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 56f5c5b87a496..221e3e7fceb1a 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,10 @@ static void register(Consumer registry) { })); 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: the directory is watched, a changed or added file (a route, a bean file, a Java class) is reloaded. Returns the pid and log file; camel_get_log, camel_get_errors 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 as one" + + " app; name files when the directory holds several apps)", 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 +247,12 @@ static void register(Consumer registry) { 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 from their files without a restart: " + + "a changed stylesheet or a dropped data file takes effect, a file consumed " + + "once is read again), 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, 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 09409e7738b6e..659d3ef3ad3c3 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,8 +56,12 @@ private IntegrationLauncher() { */ public static JsonObject run(Path directory, List files, String name, boolean dev, List extraArgs) { List cmd = new ArrayList<>(LauncherHelper.getCamelCommand()); - List 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 sources = sourceDir ? sourceFiles(directory) : files; + cmd.addAll(sourceDir ? sourceDirArguments(name, dev, extraArgs) : runArguments(sources, name, dev, extraArgs)); JsonObject result = new JsonObject(); if (sources.isEmpty()) { result.put("directory", directory.toString()); @@ -105,9 +109,13 @@ public static JsonObject run(Path directory, List files, String name, bo 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; @@ -165,6 +173,24 @@ static List sourceFiles(Path directory) { * @param extraArgs further {@code camel run} arguments * @return the arguments after the camel command itself */ + /** The {@code camel run --source-dir=.} arguments: the directory the process starts in is the app. */ + static List sourceDirArguments(String name, boolean dev, List extraArgs) { + List 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; + } + static List runArguments(List files, String name, boolean dev, List extraArgs) { List cmd = new ArrayList<>(); cmd.add("run"); @@ -204,7 +230,7 @@ private static String readTail(Path output) { * 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 +259,15 @@ public static String control(ToolContext ctx, String action) { 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 46dcd3fa9f24a..5d5fca25d53ec 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 @@ void noFilesRunsEverySourceFileInTheDirectory(@TempDir Path dir) throws Exceptio 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, From c6afdfe7397b1ca628366249b159eae89a88b4b5 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Mon, 21 Sep 2026 11:03:22 +0200 Subject: [PATCH 2/2] CAMEL-24861: address review: no empty-sources guard under --source-dir, Javadoc back on runArguments, prompt budget The top-level file guard fired for a project whose only source is a Java class under src/main/java although camel run --source-dir handles it; skipped when the directory is the app. sourceDirArguments moved above the runArguments Javadoc it had displaced. The camel_run and camel_control descriptions shortened, and the full prefix budget raised from 9200 to 9300 for the reload action (main was ~9180 already). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj --- .../core/commands/ai/AuthoringTools.java | 12 +++++------ .../core/commands/ai/IntegrationLauncher.java | 21 ++++++++++--------- .../commands/tui/AiPanelPromptBudgetTest.java | 4 +++- 3 files changed, 19 insertions(+), 18 deletions(-) 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 221e3e7fceb1a..5fade8122f97a 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 @@ static void register(Consumer registry) { })); registry.accept(tool("camel_run", - "Starts an integration with camel run in a separate process, in dev mode by default: the directory is watched, a changed or added file (a route, a bean file, a Java class) is reloaded. 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: the whole directory as one" - + " app; name files when the directory holds several apps)", + .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,10 +246,9 @@ static void register(Consumer registry) { registry.accept(tool("camel_control", "Controls a running integration: stop (graceful), kill, restart (picks up edited files without dev " - + "mode), reload (loads the routes again from their files without a restart: " - + "a changed stylesheet or a dropped data file takes effect, a file consumed " - + "once is read again), stop-routes, start-routes, reset-stats (clears " - + "statistics, routes untouched). Never stop, kill or restart unless the " + + "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) 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 659d3ef3ad3c3..7d64a6322d956 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 @@ -63,7 +63,8 @@ public static JsonObject run(Path directory, List files, String name, bo List 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 @@ -164,15 +165,6 @@ static List sourceFiles(Path directory) { return names; } - /** - * The {@code camel run} arguments for the given files, name and mode. - * - * @param files the source files, relative to the directory - * @param name the integration name, or null for the default - * @param dev whether to run in dev mode - * @param extraArgs further {@code camel run} arguments - * @return the arguments after the camel command itself - */ /** The {@code camel run --source-dir=.} arguments: the directory the process starts in is the app. */ static List sourceDirArguments(String name, boolean dev, List extraArgs) { List cmd = new ArrayList<>(); @@ -191,6 +183,15 @@ static List sourceDirArguments(String name, boolean dev, List ex return cmd; } + /** + * The {@code camel run} arguments for the given files, name and mode. + * + * @param files the source files, relative to the directory + * @param name the integration name, or null for the default + * @param dev whether to run in dev mode + * @param extraArgs further {@code camel run} arguments + * @return the arguments after the camel command itself + */ static List runArguments(List files, String name, boolean dev, List extraArgs) { List cmd = new ArrayList<>(); cmd.add("run"); 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 2bfd77cc224fd..b3ceac02cc9e1 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) {