diff --git a/crates/db/.sqlx/query-ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0.json b/crates/db/.sqlx/query-0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae.json similarity index 69% rename from crates/db/.sqlx/query-ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0.json rename to crates/db/.sqlx/query-0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae.json index b076836a5e..0b972f93a5 100644 --- a/crates/db/.sqlx/query-ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0.json +++ b/crates/db/.sqlx/query-0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "INSERT INTO projects (\n id,\n name\n ) VALUES (\n $1, $2\n )\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"", + "query": "INSERT INTO projects (\n id,\n name\n ) VALUES (\n $1, $2\n )\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"", "describe": { "columns": [ { @@ -29,18 +29,28 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], @@ -54,9 +64,11 @@ false, false, true, + true, + true, false, false ] }, - "hash": "ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0" + "hash": "0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae" } diff --git a/crates/db/.sqlx/query-88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d.json b/crates/db/.sqlx/query-4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629.json similarity index 63% rename from crates/db/.sqlx/query-88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d.json rename to crates/db/.sqlx/query-4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629.json index 4099b7f012..6a331e3c60 100644 --- a/crates/db/.sqlx/query-88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d.json +++ b/crates/db/.sqlx/query-4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1", + "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1", "describe": { "columns": [ { @@ -29,18 +29,28 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], @@ -54,9 +64,11 @@ true, true, true, + true, + true, false, false ] }, - "hash": "88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d" + "hash": "4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629" } diff --git a/crates/db/.sqlx/query-368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10.json b/crates/db/.sqlx/query-97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97.json similarity index 58% rename from crates/db/.sqlx/query-368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10.json rename to crates/db/.sqlx/query-97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97.json index 910193cc88..7977928857 100644 --- a/crates/db/.sqlx/query-368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10.json +++ b/crates/db/.sqlx/query-97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.dev_script, p.dev_script_working_dir,\n p.default_agent_working_dir,\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime\", p.updated_at as \"updated_at!: DateTime\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN workspaces w ON w.task_id = t.id\n ORDER BY w.updated_at DESC\n )\n LIMIT $1\n ", + "query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.dev_script, p.dev_script_working_dir,\n p.default_agent_working_dir, p.dev_server_timeout, p.dev_server_port,\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime\", p.updated_at as \"updated_at!: DateTime\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN workspaces w ON w.task_id = t.id\n ORDER BY w.updated_at DESC\n )\n LIMIT $1\n ", "describe": { "columns": [ { @@ -29,18 +29,28 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], @@ -54,9 +64,11 @@ true, true, true, + true, + true, false, false ] }, - "hash": "368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10" + "hash": "97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97" } diff --git a/crates/db/.sqlx/query-1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782.json b/crates/db/.sqlx/query-a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c.json similarity index 65% rename from crates/db/.sqlx/query-1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782.json rename to crates/db/.sqlx/query-a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c.json index 263900474a..8b50627fa4 100644 --- a/crates/db/.sqlx/query-1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782.json +++ b/crates/db/.sqlx/query-a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE id = $1", + "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE rowid = $1", "describe": { "columns": [ { @@ -29,18 +29,28 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], @@ -54,9 +64,11 @@ true, true, true, + true, + true, false, false ] }, - "hash": "1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782" + "hash": "a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c" } diff --git a/crates/db/.sqlx/query-17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d.json b/crates/db/.sqlx/query-c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8.json similarity index 65% rename from crates/db/.sqlx/query-17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d.json rename to crates/db/.sqlx/query-c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8.json index 4b5eed086e..7fb0f50bec 100644 --- a/crates/db/.sqlx/query-17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d.json +++ b/crates/db/.sqlx/query-c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE rowid = $1", + "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE id = $1", "describe": { "columns": [ { @@ -29,18 +29,28 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], @@ -54,9 +64,11 @@ true, true, true, + true, + true, false, false ] }, - "hash": "17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d" + "hash": "c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8" } diff --git a/crates/db/.sqlx/query-9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b.json b/crates/db/.sqlx/query-d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4.json similarity index 64% rename from crates/db/.sqlx/query-9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b.json rename to crates/db/.sqlx/query-d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4.json index 08cd108b94..25f989d5ff 100644 --- a/crates/db/.sqlx/query-9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b.json +++ b/crates/db/.sqlx/query-d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n ORDER BY created_at DESC", + "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n ORDER BY created_at DESC", "describe": { "columns": [ { @@ -29,18 +29,28 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], @@ -54,9 +64,11 @@ true, true, true, + true, + true, false, false ] }, - "hash": "9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b" + "hash": "d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4" } diff --git a/crates/db/.sqlx/query-697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919.json b/crates/db/.sqlx/query-ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97.json similarity index 55% rename from crates/db/.sqlx/query-697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919.json rename to crates/db/.sqlx/query-ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97.json index b814774124..3292258ba3 100644 --- a/crates/db/.sqlx/query-697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919.json +++ b/crates/db/.sqlx/query-ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "UPDATE projects\n SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"", + "query": "UPDATE projects\n SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5, dev_server_timeout = $6, dev_server_port = $7\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"", "describe": { "columns": [ { @@ -29,23 +29,33 @@ "type_info": "Text" }, { - "name": "remote_project_id: Uuid", + "name": "dev_server_timeout", "ordinal": 5, + "type_info": "Integer" + }, + { + "name": "dev_server_port", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "remote_project_id: Uuid", + "ordinal": 7, "type_info": "Blob" }, { "name": "created_at!: DateTime", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "updated_at!: DateTime", - "ordinal": 7, + "ordinal": 9, "type_info": "Text" } ], "parameters": { - "Right": 5 + "Right": 7 }, "nullable": [ true, @@ -54,9 +64,11 @@ true, true, true, + true, + true, false, false ] }, - "hash": "697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919" + "hash": "ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97" } diff --git a/crates/db/migrations/20260104000000_add_dev_server_timeout_to_projects.sql b/crates/db/migrations/20260104000000_add_dev_server_timeout_to_projects.sql new file mode 100644 index 0000000000..01049ead77 --- /dev/null +++ b/crates/db/migrations/20260104000000_add_dev_server_timeout_to_projects.sql @@ -0,0 +1,2 @@ +ALTER TABLE projects ADD COLUMN dev_server_timeout INTEGER DEFAULT NULL; +ALTER TABLE projects ADD COLUMN dev_server_port INTEGER DEFAULT NULL; diff --git a/crates/db/src/models/project.rs b/crates/db/src/models/project.rs index c8a61e4698..f0752467bc 100644 --- a/crates/db/src/models/project.rs +++ b/crates/db/src/models/project.rs @@ -24,6 +24,11 @@ pub struct Project { pub dev_script: Option, pub dev_script_working_dir: Option, pub default_agent_working_dir: Option, + /// Timeout in seconds before showing the "trouble previewing" help message. + /// Defaults to 30 seconds if not set. + pub dev_server_timeout: Option, + /// Optional port for the dev server. If set, this port will be used instead of auto-detection. + pub dev_server_port: Option, pub remote_project_id: Option, #[ts(type = "Date")] pub created_at: DateTime, @@ -43,6 +48,10 @@ pub struct UpdateProject { pub dev_script: Option, pub dev_script_working_dir: Option, pub default_agent_working_dir: Option, + /// Timeout in seconds before showing the "trouble previewing" help message. + pub dev_server_timeout: Option, + /// Optional port for the dev server. + pub dev_server_port: Option, } #[derive(Debug, Serialize, TS)] @@ -77,6 +86,8 @@ impl Project { dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, remote_project_id as "remote_project_id: Uuid", created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" @@ -93,7 +104,7 @@ impl Project { Project, r#" SELECT p.id as "id!: Uuid", p.name, p.dev_script, p.dev_script_working_dir, - p.default_agent_working_dir, + p.default_agent_working_dir, p.dev_server_timeout, p.dev_server_port, p.remote_project_id as "remote_project_id: Uuid", p.created_at as "created_at!: DateTime", p.updated_at as "updated_at!: DateTime" FROM projects p @@ -119,6 +130,8 @@ impl Project { dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, remote_project_id as "remote_project_id: Uuid", created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" @@ -138,6 +151,8 @@ impl Project { dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, remote_project_id as "remote_project_id: Uuid", created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" @@ -160,6 +175,8 @@ impl Project { dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, remote_project_id as "remote_project_id: Uuid", created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" @@ -190,6 +207,8 @@ impl Project { dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, remote_project_id as "remote_project_id: Uuid", created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime""#, @@ -213,17 +232,21 @@ impl Project { let dev_script = payload.dev_script.clone(); let dev_script_working_dir = payload.dev_script_working_dir.clone(); let default_agent_working_dir = payload.default_agent_working_dir.clone(); + let dev_server_timeout = payload.dev_server_timeout; + let dev_server_port = payload.dev_server_port; sqlx::query_as!( Project, r#"UPDATE projects - SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5 + SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5, dev_server_timeout = $6, dev_server_port = $7 WHERE id = $1 RETURNING id as "id!: Uuid", name, dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, remote_project_id as "remote_project_id: Uuid", created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime""#, @@ -232,6 +255,8 @@ impl Project { dev_script, dev_script_working_dir, default_agent_working_dir, + dev_server_timeout, + dev_server_port, ) .fetch_one(pool) .await diff --git a/crates/services/src/services/container.rs b/crates/services/src/services/container.rs index 5e3c4c6ebb..8d9ff35812 100644 --- a/crates/services/src/services/container.rs +++ b/crates/services/src/services/container.rs @@ -413,6 +413,8 @@ pub trait ContainerService { } else { project.default_agent_working_dir.clone() }, + dev_server_timeout: project.dev_server_timeout, + dev_server_port: project.dev_server_port, }, ) .await?; diff --git a/crates/services/src/services/project.rs b/crates/services/src/services/project.rs index 854c08f4d4..63de383b63 100644 --- a/crates/services/src/services/project.rs +++ b/crates/services/src/services/project.rs @@ -132,6 +132,8 @@ impl ProjectService { dev_script: None, dev_script_working_dir: None, default_agent_working_dir: Some(repo.name), + dev_server_timeout: None, + dev_server_port: None, }, ) .await?; diff --git a/frontend/src/components/panels/PreviewPanel.tsx b/frontend/src/components/panels/PreviewPanel.tsx index 07c45992f6..e9e9553ad5 100644 --- a/frontend/src/components/panels/PreviewPanel.tsx +++ b/frontend/src/components/panels/PreviewPanel.tsx @@ -44,7 +44,16 @@ export function PreviewPanel() { } = useDevServer(attemptId); const logStream = useLogStream(latestDevServerProcess?.id ?? ''); - const lastKnownUrl = useDevserverUrlFromLogs(logStream.logs); + const autoDetectedUrl = useDevserverUrlFromLogs(logStream.logs); + + // Use configured port if set (takes priority), otherwise use auto-detected URL + const lastKnownUrl = project?.dev_server_port + ? { + url: `http://${window.location.hostname}:${project.dev_server_port}`, + port: Number(project.dev_server_port), + scheme: 'http' as const, + } + : autoDetectedUrl; const previewState = useDevserverPreview(attemptId, { projectHasDevScript, @@ -96,21 +105,33 @@ export function PreviewPanel() { }; }, [previewState.status, previewState.url, addElement]); + // Timeout before showing the "trouble previewing" help message + // Uses project setting if configured, otherwise defaults to 30 seconds + const DEFAULT_DEV_SERVER_TIMEOUT_SECONDS = 30; + const devServerTimeoutMs = + Number(project?.dev_server_timeout ?? DEFAULT_DEV_SERVER_TIMEOUT_SECONDS) * 1000; + function startTimer() { setLoadingTimeFinished(false); setTimeout(() => { setLoadingTimeFinished(true); - }, 5000); + }, devServerTimeoutMs); } useEffect(() => { startTimer(); }, []); + const isPreviewReady = + previewState.status === 'ready' && + Boolean(previewState.url) && + !iframeError; + useEffect(() => { if ( loadingTimeFinished && !isReady && + !isPreviewReady && latestDevServerProcess && runningDevServer ) { @@ -118,11 +139,14 @@ export function PreviewPanel() { setShowLogs(true); setLoadingTimeFinished(false); } - }, [loadingTimeFinished, isReady, latestDevServerProcess, runningDevServer]); + }, [ + loadingTimeFinished, + isReady, + isPreviewReady, + latestDevServerProcess, + runningDevServer, + ]); - const isPreviewReady = - (previewState.status === 'ready' && Boolean(previewState.url)) || - (customUrl !== null && runningDevServer); const isPreviewReadyWithoutError = isPreviewReady && !iframeError; const mode = iframeError ? 'error' diff --git a/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx b/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx index 5faa7ebc20..c930989194 100644 --- a/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx +++ b/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx @@ -87,6 +87,8 @@ export function NoServerContent({ dev_script: script, dev_script_working_dir: project.dev_script_working_dir ?? null, default_agent_working_dir: project.default_agent_working_dir ?? null, + dev_server_timeout: project.dev_server_timeout ?? null, + dev_server_port: project.dev_server_port ?? null, }, }, { diff --git a/frontend/src/pages/settings/ProjectSettings.tsx b/frontend/src/pages/settings/ProjectSettings.tsx index 6e28c897b7..5c5d10f997 100644 --- a/frontend/src/pages/settings/ProjectSettings.tsx +++ b/frontend/src/pages/settings/ProjectSettings.tsx @@ -38,6 +38,8 @@ interface ProjectFormState { dev_script: string; dev_script_working_dir: string; default_agent_working_dir: string; + dev_server_timeout: string; + dev_server_port: string; } interface RepoScriptsFormState { @@ -53,6 +55,8 @@ function projectToFormState(project: Project): ProjectFormState { dev_script: project.dev_script ?? '', dev_script_working_dir: project.dev_script_working_dir ?? '', default_agent_working_dir: project.default_agent_working_dir ?? '', + dev_server_timeout: project.dev_server_timeout?.toString() ?? '', + dev_server_port: project.dev_server_port?.toString() ?? '', }; } @@ -391,12 +395,16 @@ export function ProjectSettings() { setSuccess(false); try { + const timeoutValue = draft.dev_server_timeout.trim(); + const portValue = draft.dev_server_port.trim(); const updateData: UpdateProject = { name: draft.name.trim(), dev_script: draft.dev_script.trim() || null, dev_script_working_dir: draft.dev_script_working_dir.trim() || null, default_agent_working_dir: draft.default_agent_working_dir.trim() || null, + dev_server_timeout: timeoutValue ? Number(timeoutValue) : null, + dev_server_port: portValue ? Number(portValue) : null, }; updateProject.mutate({ @@ -610,6 +618,47 @@ export function ProjectSettings() {

+
+ + + updateDraft({ dev_server_port: e.target.value }) + } + placeholder="Auto-detect" + className="font-mono w-32" + /> +

+ Port for the dev server preview. Leave empty to auto-detect + from dev server logs. +

+
+ +
+ + + updateDraft({ dev_server_timeout: e.target.value }) + } + placeholder="30" + className="font-mono w-32" + /> +

+ Time to wait before showing the "trouble previewing" + help message. Default is 30 seconds. +

+
+