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) {