davsclaus commented on code in PR #27931:
URL: https://github.com/apache/camel/pull/27931#discussion_r4290590786


##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CliRunner.java:
##########
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.dsl.jbang.core.common.Printer;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
+/**
+ * Runs a Camel CLI command in this JVM, as camel ask and the TUI do, and 
gives what a script running camel gets: the
+ * exit code, and stdout and stderr apart. The commands share the printer of 
{@link CamelJBangMain} and the picocli
+ * command tree, so one command runs at a time: the camel shell takes the same 
{@link #LOCK}.
+ */
+public final class CliRunner {
+
+    /**
+     * The lock of the commands that run in this JVM.
+     */
+    public static final Object LOCK = CamelJBangMain.class;
+
+    private CliRunner() {
+    }
+
+    /**
+     * What a command did.
+     *
+     * @param exitCode  the exit code, as camel exits with it ({@link 
CliExitCode#TIMEOUT} when it did not finish in
+     *                  time)
+     * @param stdout    the output of the command
+     * @param stderr    the errors, warnings and tips
+     * @param elapsedMs how long the command ran
+     * @param timedOut  whether the command did not finish in time, and was 
interrupted
+     */
+    public record CliResult(int exitCode, String stdout, String stderr, long 
elapsedMs, boolean timedOut) {
+
+        public boolean isSuccess() {
+            return exitCode == CliExitCode.OK;
+        }
+
+        /**
+         * The result as JSON, for an AI agent: exitCode, stdout, stderr, 
elapsedMs and timedOut, where stdout and
+         * stderr are cut at the given number of characters.
+         */
+        public JsonObject toJson(int maxChars) {
+            JsonObject answer = new JsonObject();
+            answer.put("exitCode", exitCode);
+            answer.put("stdout", cut(stdout, maxChars));
+            answer.put("stderr", cut(stderr, maxChars));
+            answer.put("elapsedMs", elapsedMs);
+            answer.put("timedOut", timedOut);
+            return answer;
+        }
+
+        private static String cut(String text, int maxChars) {
+            return text.length() > maxChars ? text.substring(0, maxChars) + 
"\n... (truncated)" : text;
+        }
+    }
+
+    /**
+     * Runs the command, and waits until it has finished.
+     *
+     * @param args the command line without camel, such as <tt>get route 
--json</tt>
+     */
+    public static CliResult run(List<String> args) {
+        return run(args, 0);
+    }
+
+    /**
+     * Runs the command, and interrupts it when it does not finish in time.
+     *
+     * @param args          the command line without camel, such as <tt>get 
route --json</tt>
+     * @param timeoutMillis how long the command may run, or 0 to wait until 
it has finished
+     */
+    public static CliResult run(List<String> args, long timeoutMillis) {
+        CommandLine commandLine = CamelJBangMain.getCommandLine();
+        if (commandLine == null) {
+            throw new IllegalStateException("The Camel CLI is not 
initialized");
+        }
+        Output output = new Output();
+        long start = System.nanoTime();
+        if (timeoutMillis <= 0) {
+            int code = execute(commandLine, args, output);
+            return output.result(code, elapsed(start), false);
+        }
+
+        FutureTask<Integer> task = new FutureTask<>(() -> execute(commandLine, 
args, output));
+        Thread thread = new Thread(task, "camel-cli-runner");
+        thread.setDaemon(true);
+        thread.start();
+        try {
+            int code = task.get(timeoutMillis, TimeUnit.MILLISECONDS);
+            return output.result(code, elapsed(start), false);
+        } catch (TimeoutException e) {
+            task.cancel(true);

Review Comment:
   Thanks, it was worse than detection: `synchronized` does not answer an 
interrupt either, so a command that timed out while it waited for the lock 
still ran once the other command ended. Fixed in the next commit:
   - The caller marks the command as abandoned when it gives up; once the 
command gets the lock it returns without running (test: `config set` that timed 
out waiting for the lock sets nothing afterwards; it did before).
   - After the interrupt the runner waits up to 2 seconds for the command to 
end, and the result says what is left: `it waited for another command that is 
still running, and does not run`, or `it has not stopped yet, and the next 
command waits until it has` (test: a command that ignores the interrupt).
   
   The lock stays the `CamelJBangMain.class` monitor shared with the camel 
shell and the AI panel; a command that never ends still holds it, but it is no 
longer silent.
   
   _Claude Code on behalf of davsclaus_



##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java:
##########
@@ -1868,6 +1874,8 @@ String callRunExample(Map<String, Object> args) throws 
Exception {
             if (outcome.log() != null) {
                 result.put("log", outcome.log());
             }
+            // a tool error (MCP isError), which still has the log that tells 
why
+            throw new ToolExecutionException(Jsoner.serialize(result));

Review Comment:
   Fixed in the next commit: `handleToolsCall` catches `ToolExecutionException` 
before the generic `Exception` and answers with its message as is (`isError: 
true`), so the JSON of a failed `tui_run_example` stays JSON. Other exceptions 
keep the `Error: ` prefix. `TuiMcpServerToolErrorTest` now expects `expression 
is required` without the prefix.
   
   _Claude Code on behalf of davsclaus_



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to