From 2403a6578f6f42678e83c9fa38c5b670d7258d03 Mon Sep 17 00:00:00 2001 From: kyteinsky Date: Mon, 12 Oct 2026 11:15:58 +0530 Subject: [PATCH 1/2] fix: convert limit to int/number Signed-off-by: kyteinsky Assisted-by: Github Copilot:claude-opus-5 --- lib/Command/Search.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Command/Search.php b/lib/Command/Search.php index f5bb156f..94ad8d1d 100644 --- a/lib/Command/Search.php +++ b/lib/Command/Search.php @@ -59,6 +59,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $limit = $input->getOption('limit'); $contextProviders = $input->getOption('context-providers'); + if ($limit !== null) { + $limit = filter_var($limit, FILTER_VALIDATE_INT); + if ($limit === false || $limit < 1) { + $output->writeln('The limit option must be a positive integer'); + return 1; + } + } + if (!empty($contextProviders)) { $contextProviders = preg_replace('/\s*,+\s*/', ',', $contextProviders); if ($contextProviders === null) { From 5dd5836c4f1e826fc66f2c0458d0791918bdc07d Mon Sep 17 00:00:00 2001 From: kyteinsky Date: Mon, 12 Oct 2026 11:16:54 +0530 Subject: [PATCH 2/2] fix: don't wait on cancelled tasks + cancel the task on exit Signed-off-by: kyteinsky Assisted-by: Github Copilot:claude-opus-5 --- lib/Command/AwaitTaskTrait.php | 109 +++++++++++++++++++++++++++++++++ lib/Command/Prompt.php | 23 ++----- lib/Command/Search.php | 23 ++----- 3 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 lib/Command/AwaitTaskTrait.php diff --git a/lib/Command/AwaitTaskTrait.php b/lib/Command/AwaitTaskTrait.php new file mode 100644 index 00000000..a8abf6c3 --- /dev/null +++ b/lib/Command/AwaitTaskTrait.php @@ -0,0 +1,109 @@ + + */ + public function getSubscribedSignals(): array { + // mirror Symfony's own precondition: subscribing without a signal registry throws + if (!\defined('SIGINT') || !SignalRegistry::isSupported()) { + return []; + } + + return [SIGINT, SIGTERM]; + } + + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false { + $taskId = $this->awaitTaskId; + $this->awaitTaskId = null; + + if ($taskId !== null && $this->awaitManager !== null) { + try { + $this->awaitManager->cancelTask($taskId); + $this->awaitOutput?->writeln('Cancelled task ' . $taskId . ''); + } catch (TaskProcessingException $e) { + $this->awaitOutput?->writeln('Failed to cancel task ' . $taskId . ': ' . $e->getMessage() . ''); + } + } + + // the task produced no result, so never report success to the caller + return 128 + $signal; + } + + /** + * Schedules the task and blocks until it reaches a terminal state. + * + * @return int the exit code of the command + */ + private function scheduleAndAwaitTask(IManager $manager, Task $task, OutputInterface $output): int { + $this->awaitManager = $manager; + $this->awaitOutput = $output; + + $manager->scheduleTask($task); + $taskId = $task->getId(); + if ($taskId === null) { + $output->writeln('Task schedule failed, taskId is null'); + return 1; + } + $this->awaitTaskId = $taskId; + + try { + // STATUS_UNKNOWN is terminal as well: the task will never make progress from there + $terminalStatuses = [ + Task::STATUS_SUCCESSFUL, + Task::STATUS_FAILED, + Task::STATUS_CANCELLED, + Task::STATUS_UNKNOWN, + ]; + + while (!in_array(($task = $manager->getTask($taskId))->getStatus(), $terminalStatuses, true)) { + sleep(2); + } + } catch (NotFoundException $e) { + $output->writeln('Task ' . $taskId . ' has disappeared: ' . $e->getMessage() . ''); + return 1; + } finally { + $this->awaitTaskId = null; + } + + switch ($task->getStatus()) { + case Task::STATUS_SUCCESSFUL: + $output->writeln(var_export($task->getOutput(), true)); + return 0; + case Task::STATUS_CANCELLED: + $output->writeln('Task ' . $taskId . ' was cancelled'); + return 1; + case Task::STATUS_UNKNOWN: + $output->writeln('Task ' . $taskId . ' is in an unknown state'); + return 1; + default: + $output->writeln('' . ($task->getErrorMessage() ?? '(empty error message)') . ''); + return 1; + } + } +} diff --git a/lib/Command/Prompt.php b/lib/Command/Prompt.php index f343d74a..85fee0e2 100644 --- a/lib/Command/Prompt.php +++ b/lib/Command/Prompt.php @@ -12,12 +12,15 @@ use OCP\TaskProcessing\IManager; use OCP\TaskProcessing\Task; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\SignalableCommandInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class Prompt extends Command { +class Prompt extends Command implements SignalableCommandInterface { + use AwaitTaskTrait; + private const CONTEXT_CHAT_TASK_TYPE_ID = Application::APP_ID . ':context_chat'; public function __construct( @@ -106,22 +109,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int ], 'context_chat', $userId); } - $this->taskProcessingManager->scheduleTask($task); - $taskId = $task->getId(); - if ($taskId === null) { - $output->writeln('Task schedule failed, taskId is null'); - return 1; - } - - while (!in_array(($task = $this->taskProcessingManager->getTask($taskId))->getStatus(), [Task::STATUS_FAILED, Task::STATUS_SUCCESSFUL], true)) { - sleep(2); - } - if ($task->getStatus() === Task::STATUS_SUCCESSFUL) { - $output->writeln(var_export($task->getOutput(), true)); - return 0; - } else { - $output->writeln('' . ($task->getErrorMessage() ?? '(empty error message)') . ''); - return 1; - } + return $this->scheduleAndAwaitTask($this->taskProcessingManager, $task, $output); } } diff --git a/lib/Command/Search.php b/lib/Command/Search.php index 94ad8d1d..af713624 100644 --- a/lib/Command/Search.php +++ b/lib/Command/Search.php @@ -12,12 +12,15 @@ use OCP\TaskProcessing\IManager; use OCP\TaskProcessing\Task; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\SignalableCommandInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class Search extends Command { +class Search extends Command implements SignalableCommandInterface { + use AwaitTaskTrait; + private const SEARCH_TASK_TYPE_ID = Application::APP_ID . ':context_chat_search'; public function __construct( @@ -95,22 +98,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int ], Application::APP_ID, $userId); } - $this->taskProcessingManager->scheduleTask($task); - $taskId = $task->getId(); - if ($taskId === null) { - $output->writeln('Task schedule failed, taskId is null'); - return 1; - } - - while (!in_array(($task = $this->taskProcessingManager->getTask($taskId))->getStatus(), [Task::STATUS_FAILED, Task::STATUS_SUCCESSFUL], true)) { - sleep(2); - } - if ($task->getStatus() === Task::STATUS_SUCCESSFUL) { - $output->writeln(var_export($task->getOutput(), true)); - return 0; - } else { - $output->writeln('' . ($task->getErrorMessage() ?? '(empty error message)') . ''); - return 1; - } + return $this->scheduleAndAwaitTask($this->taskProcessingManager, $task, $output); } }