feat: config opencode path and timeout (opencode_path, opencode_timeout)
This commit is contained in:
@@ -2,5 +2,7 @@
|
|||||||
"port": 8000,
|
"port": 8000,
|
||||||
"pipelines_dir": "./storage/pipelines/",
|
"pipelines_dir": "./storage/pipelines/",
|
||||||
"logs_dir": "./storage/logs/",
|
"logs_dir": "./storage/logs/",
|
||||||
"web_root": "../../frontend/build/web/"
|
"web_root": "../../frontend/build/web/",
|
||||||
|
"opencode_path": "opencode",
|
||||||
|
"opencode_timeout": 300
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,16 @@
|
|||||||
static const int DefaultPort = 8000;
|
static const int DefaultPort = 8000;
|
||||||
static const PIString DefaultPipelinesDir = "./storage/pipelines/";
|
static const PIString DefaultPipelinesDir = "./storage/pipelines/";
|
||||||
static const PIString DefaultLogsDir = "./storage/logs/";
|
static const PIString DefaultLogsDir = "./storage/logs/";
|
||||||
|
static const PIString DefaultOpencodePath = "opencode";
|
||||||
|
static const int DefaultOpencodeTimeout = 300;
|
||||||
|
|
||||||
ServerConfig::ServerConfig()
|
ServerConfig::ServerConfig()
|
||||||
: port_(DefaultPort)
|
: port_(DefaultPort)
|
||||||
, pipelines_dir_(DefaultPipelinesDir)
|
, pipelines_dir_(DefaultPipelinesDir)
|
||||||
, logs_dir_(DefaultLogsDir)
|
, logs_dir_(DefaultLogsDir)
|
||||||
, web_root_()
|
, web_root_()
|
||||||
|
, opencode_path_(DefaultOpencodePath)
|
||||||
|
, opencode_timeout_(DefaultOpencodeTimeout)
|
||||||
, valid_(true) {}
|
, valid_(true) {}
|
||||||
|
|
||||||
bool ServerConfig::loadFromFile(const PIString & path) {
|
bool ServerConfig::loadFromFile(const PIString & path) {
|
||||||
@@ -45,6 +49,12 @@ bool ServerConfig::loadFromFile(const PIString & path) {
|
|||||||
if (config.contains("web_root")) {
|
if (config.contains("web_root")) {
|
||||||
web_root_ = config["web_root"].toString();
|
web_root_ = config["web_root"].toString();
|
||||||
}
|
}
|
||||||
|
if (config.contains("opencode_path")) {
|
||||||
|
opencode_path_ = config["opencode_path"].toString();
|
||||||
|
}
|
||||||
|
if (config.contains("opencode_timeout")) {
|
||||||
|
opencode_timeout_ = config["opencode_timeout"].toInt();
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize directory paths
|
// Normalize directory paths
|
||||||
pipelines_dir_ = normalizeDir(pipelines_dir_);
|
pipelines_dir_ = normalizeDir(pipelines_dir_);
|
||||||
@@ -88,6 +98,14 @@ PIString ServerConfig::webRoot() const {
|
|||||||
return web_root_;
|
return web_root_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PIString ServerConfig::opencodePath() const {
|
||||||
|
return opencode_path_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ServerConfig::opencodeTimeout() const {
|
||||||
|
return opencode_timeout_;
|
||||||
|
}
|
||||||
|
|
||||||
bool ServerConfig::isValid() const {
|
bool ServerConfig::isValid() const {
|
||||||
return valid_;
|
return valid_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public:
|
|||||||
PIString pipelinesDir() const;
|
PIString pipelinesDir() const;
|
||||||
PIString logsDir() const;
|
PIString logsDir() const;
|
||||||
PIString webRoot() const;
|
PIString webRoot() const;
|
||||||
|
PIString opencodePath() const;
|
||||||
|
int opencodeTimeout() const;
|
||||||
|
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
|
||||||
@@ -23,6 +25,8 @@ private:
|
|||||||
PIString pipelines_dir_;
|
PIString pipelines_dir_;
|
||||||
PIString logs_dir_;
|
PIString logs_dir_;
|
||||||
PIString web_root_;
|
PIString web_root_;
|
||||||
|
PIString opencode_path_;
|
||||||
|
int opencode_timeout_;
|
||||||
bool valid_;
|
bool valid_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ int main(int argc, char * argv[]) {
|
|||||||
piCout << "Pipelines dir: " << config.pipelinesDir();
|
piCout << "Pipelines dir: " << config.pipelinesDir();
|
||||||
piCout << "Logs dir: " << config.logsDir();
|
piCout << "Logs dir: " << config.logsDir();
|
||||||
piCout << "Web root: " << (config.webRoot().isEmpty() ? "(not set)" : config.webRoot());
|
piCout << "Web root: " << (config.webRoot().isEmpty() ? "(not set)" : config.webRoot());
|
||||||
|
piCout << "Opencode path: " << config.opencodePath();
|
||||||
|
piCout << "Opencode timeout: " << config.opencodeTimeout() << "s";
|
||||||
|
|
||||||
// Handle signals
|
// Handle signals
|
||||||
PISignals::setSlot([](PISignals::Signal s) {
|
PISignals::setSlot([](PISignals::Signal s) {
|
||||||
@@ -37,7 +39,12 @@ int main(int argc, char * argv[]) {
|
|||||||
PISignals::grabSignals(PISignals::Interrupt);
|
PISignals::grabSignals(PISignals::Interrupt);
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
Server server(static_cast<ushort>(config.port()), config.pipelinesDir(), config.logsDir(), config.webRoot());
|
Server server(static_cast<ushort>(config.port()),
|
||||||
|
config.pipelinesDir(),
|
||||||
|
config.logsDir(),
|
||||||
|
config.webRoot(),
|
||||||
|
config.opencodePath(),
|
||||||
|
config.opencodeTimeout());
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
// Wait for exit
|
// Wait for exit
|
||||||
|
|||||||
@@ -86,9 +86,14 @@ RunState PipelineRunner::runStateFromJSON(const PIJSON & j) {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineRunner::PipelineRunner(const PIString & pipelines_dir, const PIString & logs_dir)
|
PipelineRunner::PipelineRunner(const PIString & pipelines_dir,
|
||||||
|
const PIString & logs_dir,
|
||||||
|
const PIString & opencode_path,
|
||||||
|
int opencode_timeout)
|
||||||
: pipelines_dir_(pipelines_dir)
|
: pipelines_dir_(pipelines_dir)
|
||||||
, logs_dir_(logs_dir) {}
|
, logs_dir_(logs_dir)
|
||||||
|
, opencode_path_(opencode_path)
|
||||||
|
, opencode_timeout_(opencode_timeout) {}
|
||||||
|
|
||||||
PipelineRunner::~PipelineRunner() {
|
PipelineRunner::~PipelineRunner() {
|
||||||
for (int i = 0; i < threads_.size(); ++i) {
|
for (int i = 0; i < threads_.size(); ++i) {
|
||||||
@@ -212,7 +217,7 @@ void PipelineRunner::executePipeline(const PIString & run_id, const Pipeline & p
|
|||||||
PIStringList args;
|
PIStringList args;
|
||||||
args << "run" << prompt.text << "--title" << prompt.title;
|
args << "run" << prompt.text << "--title" << prompt.title;
|
||||||
|
|
||||||
ProcessResult result = ProcessExecutor::run("opencode", args, 300, pipeline.working_dir);
|
ProcessResult result = ProcessExecutor::run(opencode_path_, args, opencode_timeout_, pipeline.working_dir);
|
||||||
int rc = result.exitCode;
|
int rc = result.exitCode;
|
||||||
PIString output = result.output;
|
PIString output = result.output;
|
||||||
PIString error = result.error;
|
PIString error = result.error;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class PipelineRunner: public PIObject {
|
|||||||
PIOBJECT(PipelineRunner)
|
PIOBJECT(PipelineRunner)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PipelineRunner(const PIString & pipelines_dir, const PIString & logs_dir);
|
PipelineRunner(const PIString & pipelines_dir, const PIString & logs_dir, const PIString & opencode_path, int opencode_timeout);
|
||||||
~PipelineRunner();
|
~PipelineRunner();
|
||||||
|
|
||||||
// Start a pipeline run, returns run_id or empty string on error
|
// Start a pipeline run, returns run_id or empty string on error
|
||||||
@@ -76,6 +76,8 @@ private:
|
|||||||
|
|
||||||
const PIString pipelines_dir_;
|
const PIString pipelines_dir_;
|
||||||
const PIString logs_dir_;
|
const PIString logs_dir_;
|
||||||
|
const PIString opencode_path_;
|
||||||
|
const int opencode_timeout_;
|
||||||
|
|
||||||
PIMutex mutex_;
|
PIMutex mutex_;
|
||||||
PIMap<PIString, RunState> runs_;
|
PIMap<PIString, RunState> runs_;
|
||||||
|
|||||||
@@ -31,12 +31,19 @@ const PIMap<PIString, PIString> StaticContentTypes{
|
|||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Server::Server(ushort port, const PIString & pipelines_dir, const PIString & logs_dir, const PIString & web_root)
|
Server::Server(ushort port,
|
||||||
|
const PIString & pipelines_dir,
|
||||||
|
const PIString & logs_dir,
|
||||||
|
const PIString & web_root,
|
||||||
|
const PIString & opencode_path,
|
||||||
|
int opencode_timeout)
|
||||||
: port_(port)
|
: port_(port)
|
||||||
, pipelines_dir_(pipelines_dir)
|
, pipelines_dir_(pipelines_dir)
|
||||||
, logs_dir_(logs_dir)
|
, logs_dir_(logs_dir)
|
||||||
, web_root_(web_root)
|
, web_root_(web_root)
|
||||||
, runner_(pipelines_dir, logs_dir) {
|
, opencode_path_(opencode_path)
|
||||||
|
, opencode_timeout_(opencode_timeout)
|
||||||
|
, runner_(pipelines_dir, logs_dir, opencode_path, opencode_timeout) {
|
||||||
httpserver_ = new PIHTTPServer();
|
httpserver_ = new PIHTTPServer();
|
||||||
|
|
||||||
// CORS headers
|
// CORS headers
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ class Server: public PIObject {
|
|||||||
PIOBJECT(Server)
|
PIOBJECT(Server)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Server(ushort port, const PIString & pipelines_dir, const PIString & logs_dir, const PIString & web_root = PIString());
|
Server(ushort port,
|
||||||
|
const PIString & pipelines_dir,
|
||||||
|
const PIString & logs_dir,
|
||||||
|
const PIString & web_root,
|
||||||
|
const PIString & opencode_path,
|
||||||
|
int opencode_timeout);
|
||||||
~Server();
|
~Server();
|
||||||
|
|
||||||
bool start();
|
bool start();
|
||||||
@@ -34,6 +39,8 @@ private:
|
|||||||
PIString pipelines_dir_;
|
PIString pipelines_dir_;
|
||||||
PIString logs_dir_;
|
PIString logs_dir_;
|
||||||
PIString web_root_;
|
PIString web_root_;
|
||||||
|
PIString opencode_path_;
|
||||||
|
int opencode_timeout_;
|
||||||
PIHTTPServer * httpserver_;
|
PIHTTPServer * httpserver_;
|
||||||
PipelineRunner runner_;
|
PipelineRunner runner_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ static Pipeline makeEchoPipeline() {
|
|||||||
|
|
||||||
TEST(RunnerTest, StartRunNotFound) {
|
TEST(RunnerTest, StartRunNotFound) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
PIString runId = runner.startRun("nonexistent");
|
PIString runId = runner.startRun("nonexistent");
|
||||||
EXPECT_TRUE(runId.isEmpty());
|
EXPECT_TRUE(runId.isEmpty());
|
||||||
@@ -72,7 +72,7 @@ TEST(RunnerTest, StartRunNotFound) {
|
|||||||
|
|
||||||
TEST(RunnerTest, StartRunAndGetState) {
|
TEST(RunnerTest, StartRunAndGetState) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
// Create a pipeline that runs 'echo hello' (uses shell command, not opencode)
|
// Create a pipeline that runs 'echo hello' (uses shell command, not opencode)
|
||||||
Pipeline pl;
|
Pipeline pl;
|
||||||
@@ -108,7 +108,7 @@ TEST(RunnerTest, StartRunAndGetState) {
|
|||||||
|
|
||||||
TEST(RunnerTest, GetStateNotFound) {
|
TEST(RunnerTest, GetStateNotFound) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
RunState state = runner.getRunState("nonexistent");
|
RunState state = runner.getRunState("nonexistent");
|
||||||
EXPECT_TRUE(state.run_id.isEmpty());
|
EXPECT_TRUE(state.run_id.isEmpty());
|
||||||
@@ -118,7 +118,7 @@ TEST(RunnerTest, GetStateNotFound) {
|
|||||||
|
|
||||||
TEST(RunnerTest, IsRunActive) {
|
TEST(RunnerTest, IsRunActive) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
Pipeline pl;
|
Pipeline pl;
|
||||||
pl.id = "active-test";
|
pl.id = "active-test";
|
||||||
@@ -154,7 +154,7 @@ TEST(RunnerTest, RunStatusToString) {
|
|||||||
TEST(PersistenceTest, SaveLoadRoundTrip) {
|
TEST(PersistenceTest, SaveLoadRoundTrip) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
|
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
// Test loadRunState on non-existent file
|
// Test loadRunState on non-existent file
|
||||||
RunState empty = runner.loadRunState("nonexistent");
|
RunState empty = runner.loadRunState("nonexistent");
|
||||||
@@ -243,7 +243,7 @@ TEST(PersistenceTest, RecoverRuns) {
|
|||||||
PIFile::writeAll(completedStatePath, j2.toJSON(PIJSON::Tree).toUTF8());
|
PIFile::writeAll(completedStatePath, j2.toJSON(PIJSON::Tree).toUTF8());
|
||||||
|
|
||||||
// New runner should recover only the running state
|
// New runner should recover only the running state
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
runner.recoverRuns();
|
runner.recoverRuns();
|
||||||
|
|
||||||
RunState recovered = runner.getRunState(runId);
|
RunState recovered = runner.getRunState(runId);
|
||||||
@@ -260,7 +260,7 @@ TEST(PersistenceTest, RecoverRuns) {
|
|||||||
TEST(PersistenceTest, CompletedRunStatePersisted) {
|
TEST(PersistenceTest, CompletedRunStatePersisted) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
|
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
Pipeline pl;
|
Pipeline pl;
|
||||||
pl.id = "persist-test";
|
pl.id = "persist-test";
|
||||||
@@ -317,7 +317,7 @@ static Pipeline makeFakePipeline(const PIString & id, const PIString & name, int
|
|||||||
|
|
||||||
TEST(FakeRunnerTest, FullPipelineCompletes) {
|
TEST(FakeRunnerTest, FullPipelineCompletes) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
Pipeline pl = makeFakePipeline("fake-full", "Fake Full Pipeline", 3);
|
Pipeline pl = makeFakePipeline("fake-full", "Fake Full Pipeline", 3);
|
||||||
savePipeline(testPipelinesDir, pl);
|
savePipeline(testPipelinesDir, pl);
|
||||||
@@ -357,7 +357,7 @@ TEST(FakeRunnerTest, FullPipelineCompletes) {
|
|||||||
|
|
||||||
TEST(FakeRunnerTest, RunStatusTransitions) {
|
TEST(FakeRunnerTest, RunStatusTransitions) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
Pipeline pl = makeFakePipeline("fake-transition", "Fake Transition Pipeline", 2);
|
Pipeline pl = makeFakePipeline("fake-transition", "Fake Transition Pipeline", 2);
|
||||||
savePipeline(testPipelinesDir, pl);
|
savePipeline(testPipelinesDir, pl);
|
||||||
@@ -397,7 +397,7 @@ TEST(FakeRunnerTest, RunStatusTransitions) {
|
|||||||
|
|
||||||
TEST(FakeRunnerTest, StepOutputContainsFakeMarker) {
|
TEST(FakeRunnerTest, StepOutputContainsFakeMarker) {
|
||||||
setupDirs();
|
setupDirs();
|
||||||
PipelineRunner runner(testPipelinesDir, testLogsDir);
|
PipelineRunner runner(testPipelinesDir, testLogsDir, "opencode", 300);
|
||||||
|
|
||||||
Pipeline pl = makeFakePipeline("fake-output", "Fake Output Pipeline", 2);
|
Pipeline pl = makeFakePipeline("fake-output", "Fake Output Pipeline", 2);
|
||||||
savePipeline(testPipelinesDir, pl);
|
savePipeline(testPipelinesDir, pl);
|
||||||
|
|||||||
Reference in New Issue
Block a user