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 c1176d685d12e..cd721d364c223 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 @@ -841,6 +841,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 30f7ee2a02e2a..fb83a312dd512 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 684cc67872a2d..04b622cc58f7e 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 (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 @@ 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 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 09409e7738b6e..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 @@ -56,10 +56,15 @@ 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()) { + // 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 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; @@ -156,6 +165,24 @@ static List sourceFiles(Path directory) { return names; } + /** 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; + } + /** * The {@code camel run} arguments for the given files, name and mode. * @@ -204,7 +231,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 +260,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, 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) {