Run setup or cleanup scripts (vibe-kanban) (#1428)
* I've successfully implemented the two new API endpoints for running setup and cleanup scripts on task attempts. Here's a summary of the changes:
## Summary
### Files Modified
1. **`crates/server/src/routes/task_attempts.rs`**
- Added `RunScriptError` enum type for error handling when no script is configured
- Added `run_setup_script` handler - POST endpoint to run a project's setup script
- Added `run_cleanup_script` handler - POST endpoint to run a project's cleanup script
- Registered both routes: `/run-setup-script` and `/run-cleanup-script`
2. **`crates/server/src/bin/generate_types.rs`**
- Added `RunScriptError` to the list of types to export to TypeScript
3. **`shared/types.ts`** (auto-generated)
- Now includes the `RunScriptError` TypeScript type
### New Endpoints
- **`POST /api/task-attempts/{id}/run-setup-script`** - Runs the project's setup script in the task attempt's worktree
- **`POST /api/task-attempts/{id}/run-cleanup-script`** - Runs the project's cleanup script in the task attempt's worktree
Both endpoints:
- Return `ExecutionProcess` on success with the created execution process
- Return `RunScriptError::NoScriptConfigured` if the project doesn't have the respective script configured
- Track analytics events (`setup_script_executed` / `cleanup_script_executed`)
- Use the existing `ScriptContext::SetupScript` / `ScriptContext::CleanupScript` and `ExecutionProcessRunReason::SetupScript` / `ExecutionProcessRunReason::CleanupScript` patterns
* Done! I've added the running process check to both `run_setup_script` and `run_cleanup_script` endpoints. Here's a summary of the changes:
## Summary
### Files Modified
1. **`crates/db/src/models/execution_process.rs`**
- Added `has_running_non_dev_server_processes` method that checks if any running processes (excluding dev servers) exist for a task attempt
2. **`crates/server/src/routes/task_attempts.rs`**
- Added `ProcessAlreadyRunning` variant to `RunScriptError` enum
- Added check at the start of both `run_setup_script` and `run_cleanup_script` handlers to return an error if non-dev-server processes are already running
3. **`shared/types.ts`** (auto-generated)
- `RunScriptError` now includes both `no_script_configured` and `process_already_running` variants
### Behavior
Both endpoints now:
1. First check if any non-dev-server processes (coding agents, setup/cleanup scripts) are running on the task attempt
2. If running processes exist, return `RunScriptError::ProcessAlreadyRunning`
3. Otherwise proceed with script execution as before
* Cleanup script changes for task attempt cdb9cbd9-a6e7-4936-8e75-6dd837c634f3
* Done! I've implemented the frontend changes for the setup/cleanup script dropdown. Here's a summary:
## Summary of Frontend Changes
### Files Modified
1. **`frontend/src/lib/api.ts`**
- Added `RunScriptError` to imports from shared types
- Added `runSetupScript` and `runCleanupScript` methods to `attemptsApi`
2. **`frontend/src/components/tasks/TaskFollowUpSection.tsx`**
- Added imports for `Terminal` icon, `DropdownMenu`, and `Tooltip` components
- Added `attemptsApi` import
- Updated `useProject()` to also get `project` data
- Added computed values: `hasSetupScript`, `hasCleanupScript`, `hasAnyScript`
- Added handlers: `handleRunSetupScript`, `handleRunCleanupScript`
- Added dropdown button with Terminal icon after the Attach button
- Dropdown only shows if project has at least one script configured
- Menu items are disabled with tooltips when scripts are not configured
- Button is disabled when `isAttemptRunning` is true
3. **`frontend/src/i18n/locales/en/tasks.json`**
- Added translation strings for the dropdown menu items and tooltips
### Behavior
- **Dropdown visibility**: Only shown if project has `setup_script` or `cleanup_script` configured
- **Button disabled state**: When `isAttemptRunning` is true (tooltip explains why)
- **Menu item disabled states**:
- "Run setup script" disabled if no `setup_script` (tooltip explains)
- "Run cleanup script" disabled if no `cleanup_script` (tooltip explains)
- **After clicking**: Script starts executing and `isAttemptRunning` becomes true automatically, preventing further interactions
* Done! I've added translations for all languages:
- **Spanish (es)**:
- "Ejecutar script de configuración" / "Ejecutar script de limpieza"
- Tooltips in Spanish
- **Japanese (ja)**:
- "セットアップスクリプトを実行" / "クリーンアップスクリプトを実行"
- Tooltips in Japanese
- **Korean (ko)**:
- "설정 스크립트 실행" / "정리 스크립트 실행"
- Tooltips in Korean
* Done! I've removed all the fallback strings from the `t()` calls. All 5 translation calls now use just the key:
- `{t('followUp.scriptsDisabledWhileRunning')}`
- `{t('followUp.runSetupScript')}`
- `{t('followUp.noSetupScript')}`
- `{t('followUp.runCleanupScript')}`
- `{t('followUp.noCleanupScript')}`
This commit is contained in:
committed by
GitHub
parent
0d2e77dceb
commit
32c689dfc5
@@ -301,6 +301,24 @@ impl ExecutionProcess {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if there are running processes (excluding dev servers) for a task attempt
|
||||||
|
pub async fn has_running_non_dev_server_processes(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
task_attempt_id: Uuid,
|
||||||
|
) -> Result<bool, sqlx::Error> {
|
||||||
|
let count: i64 = sqlx::query_scalar(
|
||||||
|
r#"SELECT COUNT(*)
|
||||||
|
FROM execution_processes
|
||||||
|
WHERE task_attempt_id = ?
|
||||||
|
AND status = 'running'
|
||||||
|
AND run_reason != 'devserver'"#,
|
||||||
|
)
|
||||||
|
.bind(task_attempt_id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(count > 0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Find latest session_id by task attempt (simple scalar query)
|
/// Find latest session_id by task attempt (simple scalar query)
|
||||||
pub async fn find_latest_session_id_by_task_attempt(
|
pub async fn find_latest_session_id_by_task_attempt(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ fn generate_types_content() -> String {
|
|||||||
server::routes::task_attempts::PushError::decl(),
|
server::routes::task_attempts::PushError::decl(),
|
||||||
server::routes::task_attempts::CreatePrError::decl(),
|
server::routes::task_attempts::CreatePrError::decl(),
|
||||||
server::routes::task_attempts::BranchStatus::decl(),
|
server::routes::task_attempts::BranchStatus::decl(),
|
||||||
|
server::routes::task_attempts::RunScriptError::decl(),
|
||||||
services::services::filesystem::DirectoryEntry::decl(),
|
services::services::filesystem::DirectoryEntry::decl(),
|
||||||
services::services::filesystem::DirectoryListResponse::decl(),
|
services::services::filesystem::DirectoryListResponse::decl(),
|
||||||
services::services::config::Config::decl(),
|
services::services::config::Config::decl(),
|
||||||
|
|||||||
@@ -1507,6 +1507,156 @@ pub async fn attach_existing_pr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||||
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
|
#[ts(tag = "type", rename_all = "snake_case")]
|
||||||
|
pub enum RunScriptError {
|
||||||
|
NoScriptConfigured,
|
||||||
|
ProcessAlreadyRunning,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn run_setup_script(
|
||||||
|
Extension(task_attempt): Extension<TaskAttempt>,
|
||||||
|
State(deployment): State<DeploymentImpl>,
|
||||||
|
) -> Result<ResponseJson<ApiResponse<ExecutionProcess, RunScriptError>>, ApiError> {
|
||||||
|
// Check if any non-dev-server processes are already running
|
||||||
|
if ExecutionProcess::has_running_non_dev_server_processes(
|
||||||
|
&deployment.db().pool,
|
||||||
|
task_attempt.id,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Ok(ResponseJson(ApiResponse::error_with_data(
|
||||||
|
RunScriptError::ProcessAlreadyRunning,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure worktree exists
|
||||||
|
let _ = ensure_worktree_path(&deployment, &task_attempt).await?;
|
||||||
|
|
||||||
|
// Get parent task and project
|
||||||
|
let task = task_attempt
|
||||||
|
.parent_task(&deployment.db().pool)
|
||||||
|
.await?
|
||||||
|
.ok_or(SqlxError::RowNotFound)?;
|
||||||
|
|
||||||
|
let project = task
|
||||||
|
.parent_project(&deployment.db().pool)
|
||||||
|
.await?
|
||||||
|
.ok_or(SqlxError::RowNotFound)?;
|
||||||
|
|
||||||
|
// Check if setup script is configured
|
||||||
|
let Some(setup_script) = project.setup_script else {
|
||||||
|
return Ok(ResponseJson(ApiResponse::error_with_data(
|
||||||
|
RunScriptError::NoScriptConfigured,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and execute the setup script action
|
||||||
|
let executor_action = ExecutorAction::new(
|
||||||
|
ExecutorActionType::ScriptRequest(ScriptRequest {
|
||||||
|
script: setup_script,
|
||||||
|
language: ScriptRequestLanguage::Bash,
|
||||||
|
context: ScriptContext::SetupScript,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let execution_process = deployment
|
||||||
|
.container()
|
||||||
|
.start_execution(
|
||||||
|
&task_attempt,
|
||||||
|
&executor_action,
|
||||||
|
&ExecutionProcessRunReason::SetupScript,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
deployment
|
||||||
|
.track_if_analytics_allowed(
|
||||||
|
"setup_script_executed",
|
||||||
|
serde_json::json!({
|
||||||
|
"task_id": task.id.to_string(),
|
||||||
|
"project_id": project.id.to_string(),
|
||||||
|
"attempt_id": task_attempt.id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(ResponseJson(ApiResponse::success(execution_process)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn run_cleanup_script(
|
||||||
|
Extension(task_attempt): Extension<TaskAttempt>,
|
||||||
|
State(deployment): State<DeploymentImpl>,
|
||||||
|
) -> Result<ResponseJson<ApiResponse<ExecutionProcess, RunScriptError>>, ApiError> {
|
||||||
|
// Check if any non-dev-server processes are already running
|
||||||
|
if ExecutionProcess::has_running_non_dev_server_processes(
|
||||||
|
&deployment.db().pool,
|
||||||
|
task_attempt.id,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Ok(ResponseJson(ApiResponse::error_with_data(
|
||||||
|
RunScriptError::ProcessAlreadyRunning,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure worktree exists
|
||||||
|
let _ = ensure_worktree_path(&deployment, &task_attempt).await?;
|
||||||
|
|
||||||
|
// Get parent task and project
|
||||||
|
let task = task_attempt
|
||||||
|
.parent_task(&deployment.db().pool)
|
||||||
|
.await?
|
||||||
|
.ok_or(SqlxError::RowNotFound)?;
|
||||||
|
|
||||||
|
let project = task
|
||||||
|
.parent_project(&deployment.db().pool)
|
||||||
|
.await?
|
||||||
|
.ok_or(SqlxError::RowNotFound)?;
|
||||||
|
|
||||||
|
// Check if cleanup script is configured
|
||||||
|
let Some(cleanup_script) = project.cleanup_script else {
|
||||||
|
return Ok(ResponseJson(ApiResponse::error_with_data(
|
||||||
|
RunScriptError::NoScriptConfigured,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and execute the cleanup script action
|
||||||
|
let executor_action = ExecutorAction::new(
|
||||||
|
ExecutorActionType::ScriptRequest(ScriptRequest {
|
||||||
|
script: cleanup_script,
|
||||||
|
language: ScriptRequestLanguage::Bash,
|
||||||
|
context: ScriptContext::CleanupScript,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let execution_process = deployment
|
||||||
|
.container()
|
||||||
|
.start_execution(
|
||||||
|
&task_attempt,
|
||||||
|
&executor_action,
|
||||||
|
&ExecutionProcessRunReason::CleanupScript,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
deployment
|
||||||
|
.track_if_analytics_allowed(
|
||||||
|
"cleanup_script_executed",
|
||||||
|
serde_json::json!({
|
||||||
|
"task_id": task.id.to_string(),
|
||||||
|
"project_id": project.id.to_string(),
|
||||||
|
"attempt_id": task_attempt.id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(ResponseJson(ApiResponse::success(execution_process)))
|
||||||
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn gh_cli_setup_handler(
|
pub async fn gh_cli_setup_handler(
|
||||||
Extension(task_attempt): Extension<TaskAttempt>,
|
Extension(task_attempt): Extension<TaskAttempt>,
|
||||||
@@ -1552,6 +1702,8 @@ pub fn router(deployment: &DeploymentImpl) -> Router<DeploymentImpl> {
|
|||||||
.route("/gh-cli-setup", post(gh_cli_setup_handler))
|
.route("/gh-cli-setup", post(gh_cli_setup_handler))
|
||||||
.route("/commit-compare", get(compare_commit_to_head))
|
.route("/commit-compare", get(compare_commit_to_head))
|
||||||
.route("/start-dev-server", post(start_dev_server))
|
.route("/start-dev-server", post(start_dev_server))
|
||||||
|
.route("/run-setup-script", post(run_setup_script))
|
||||||
|
.route("/run-cleanup-script", post(run_cleanup_script))
|
||||||
.route("/branch-status", get(get_task_attempt_branch_status))
|
.route("/branch-status", get(get_task_attempt_branch_status))
|
||||||
.route("/diff/ws", get(stream_task_attempt_diff_ws))
|
.route("/diff/ws", get(stream_task_attempt_diff_ws))
|
||||||
.route("/merge", post(merge_task_attempt))
|
.route("/merge", post(merge_task_attempt))
|
||||||
|
|||||||
@@ -6,9 +6,22 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
X,
|
X,
|
||||||
Paperclip,
|
Paperclip,
|
||||||
|
Terminal,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip';
|
||||||
//
|
//
|
||||||
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||||
import { ScratchType, type TaskWithAttemptStatus } from 'shared/types';
|
import { ScratchType, type TaskWithAttemptStatus } from 'shared/types';
|
||||||
@@ -42,7 +55,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useScratch } from '@/hooks/useScratch';
|
import { useScratch } from '@/hooks/useScratch';
|
||||||
import { useDebouncedCallback } from '@/hooks/useDebouncedCallback';
|
import { useDebouncedCallback } from '@/hooks/useDebouncedCallback';
|
||||||
import { useQueueStatus } from '@/hooks/useQueueStatus';
|
import { useQueueStatus } from '@/hooks/useQueueStatus';
|
||||||
import { imagesApi } from '@/lib/api';
|
import { imagesApi, attemptsApi } from '@/lib/api';
|
||||||
|
|
||||||
interface TaskFollowUpSectionProps {
|
interface TaskFollowUpSectionProps {
|
||||||
task: TaskWithAttemptStatus;
|
task: TaskWithAttemptStatus;
|
||||||
@@ -54,7 +67,7 @@ export function TaskFollowUpSection({
|
|||||||
selectedAttemptId,
|
selectedAttemptId,
|
||||||
}: TaskFollowUpSectionProps) {
|
}: TaskFollowUpSectionProps) {
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const { projectId } = useProject();
|
const { projectId, project } = useProject();
|
||||||
|
|
||||||
const { isAttemptRunning, stopExecution, isStopping, processes } =
|
const { isAttemptRunning, stopExecution, isStopping, processes } =
|
||||||
useAttemptExecution(selectedAttemptId, task.id);
|
useAttemptExecution(selectedAttemptId, task.id);
|
||||||
@@ -352,6 +365,29 @@ export function TaskFollowUpSection({
|
|||||||
]);
|
]);
|
||||||
const isEditable = !isRetryActive && !hasPendingApproval;
|
const isEditable = !isRetryActive && !hasPendingApproval;
|
||||||
|
|
||||||
|
// Script availability
|
||||||
|
const hasSetupScript = Boolean(project?.setup_script);
|
||||||
|
const hasCleanupScript = Boolean(project?.cleanup_script);
|
||||||
|
const hasAnyScript = hasSetupScript || hasCleanupScript;
|
||||||
|
|
||||||
|
const handleRunSetupScript = useCallback(async () => {
|
||||||
|
if (!selectedAttemptId || isAttemptRunning || !hasSetupScript) return;
|
||||||
|
try {
|
||||||
|
await attemptsApi.runSetupScript(selectedAttemptId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to run setup script:', error);
|
||||||
|
}
|
||||||
|
}, [selectedAttemptId, isAttemptRunning, hasSetupScript]);
|
||||||
|
|
||||||
|
const handleRunCleanupScript = useCallback(async () => {
|
||||||
|
if (!selectedAttemptId || isAttemptRunning || !hasCleanupScript) return;
|
||||||
|
try {
|
||||||
|
await attemptsApi.runCleanupScript(selectedAttemptId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to run cleanup script:', error);
|
||||||
|
}
|
||||||
|
}, [selectedAttemptId, isAttemptRunning, hasCleanupScript]);
|
||||||
|
|
||||||
// Handler to queue the current message for execution after agent finishes
|
// Handler to queue the current message for execution after agent finishes
|
||||||
const handleQueueMessage = useCallback(async () => {
|
const handleQueueMessage = useCallback(async () => {
|
||||||
if (
|
if (
|
||||||
@@ -685,6 +721,73 @@ export function TaskFollowUpSection({
|
|||||||
<Paperclip className="h-4 w-4" />
|
<Paperclip className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* Scripts dropdown - only show if project has any scripts */}
|
||||||
|
{hasAnyScript && (
|
||||||
|
<DropdownMenu>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
disabled={isAttemptRunning}
|
||||||
|
aria-label="Run scripts"
|
||||||
|
>
|
||||||
|
<Terminal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{isAttemptRunning && (
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{t('followUp.scriptsDisabledWhileRunning')}
|
||||||
|
</TooltipContent>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>
|
||||||
|
<DropdownMenuItem
|
||||||
|
disabled={!hasSetupScript}
|
||||||
|
onClick={handleRunSetupScript}
|
||||||
|
>
|
||||||
|
{t('followUp.runSetupScript')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{!hasSetupScript && (
|
||||||
|
<TooltipContent side="left">
|
||||||
|
{t('followUp.noSetupScript')}
|
||||||
|
</TooltipContent>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>
|
||||||
|
<DropdownMenuItem
|
||||||
|
disabled={!hasCleanupScript}
|
||||||
|
onClick={handleRunCleanupScript}
|
||||||
|
>
|
||||||
|
{t('followUp.runCleanupScript')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{!hasCleanupScript && (
|
||||||
|
<TooltipContent side="left">
|
||||||
|
{t('followUp.noCleanupScript')}
|
||||||
|
</TooltipContent>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
|
||||||
{isAttemptRunning ? (
|
{isAttemptRunning ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Queue/Cancel Queue button when running */}
|
{/* Queue/Cancel Queue button when running */}
|
||||||
|
|||||||
@@ -153,7 +153,12 @@
|
|||||||
"queueForNextTurn": "Queue for next turn",
|
"queueForNextTurn": "Queue for next turn",
|
||||||
"queue": "Queue",
|
"queue": "Queue",
|
||||||
"cancelQueue": "Cancel Queue",
|
"cancelQueue": "Cancel Queue",
|
||||||
"queuedMessage": "Message queued - will execute when current run finishes"
|
"queuedMessage": "Message queued - will execute when current run finishes",
|
||||||
|
"runSetupScript": "Run setup script",
|
||||||
|
"runCleanupScript": "Run cleanup script",
|
||||||
|
"noSetupScript": "No setup script configured for this project",
|
||||||
|
"noCleanupScript": "No cleanup script configured for this project",
|
||||||
|
"scriptsDisabledWhileRunning": "Cannot run scripts while a process is running"
|
||||||
},
|
},
|
||||||
"todos": {
|
"todos": {
|
||||||
"title_one": "Todos ({{count}})",
|
"title_one": "Todos ({{count}})",
|
||||||
|
|||||||
@@ -183,7 +183,12 @@
|
|||||||
"unqueuing": "Unqueuing…",
|
"unqueuing": "Unqueuing…",
|
||||||
"queue": "Encolar",
|
"queue": "Encolar",
|
||||||
"cancelQueue": "Cancelar cola",
|
"cancelQueue": "Cancelar cola",
|
||||||
"queuedMessage": "Mensaje en cola - se ejecutará cuando finalice la ejecución actual"
|
"queuedMessage": "Mensaje en cola - se ejecutará cuando finalice la ejecución actual",
|
||||||
|
"runSetupScript": "Ejecutar script de configuración",
|
||||||
|
"runCleanupScript": "Ejecutar script de limpieza",
|
||||||
|
"noSetupScript": "No hay script de configuración configurado para este proyecto",
|
||||||
|
"noCleanupScript": "No hay script de limpieza configurado para este proyecto",
|
||||||
|
"scriptsDisabledWhileRunning": "No se pueden ejecutar scripts mientras un proceso está en ejecución"
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"branch": {
|
"branch": {
|
||||||
|
|||||||
@@ -183,7 +183,12 @@
|
|||||||
"unqueuing": "Unqueuing…",
|
"unqueuing": "Unqueuing…",
|
||||||
"queue": "キューに追加",
|
"queue": "キューに追加",
|
||||||
"cancelQueue": "キューをキャンセル",
|
"cancelQueue": "キューをキャンセル",
|
||||||
"queuedMessage": "メッセージがキューに追加されました - 現在の実行が完了すると実行されます"
|
"queuedMessage": "メッセージがキューに追加されました - 現在の実行が完了すると実行されます",
|
||||||
|
"runSetupScript": "セットアップスクリプトを実行",
|
||||||
|
"runCleanupScript": "クリーンアップスクリプトを実行",
|
||||||
|
"noSetupScript": "このプロジェクトにセットアップスクリプトが設定されていません",
|
||||||
|
"noCleanupScript": "このプロジェクトにクリーンアップスクリプトが設定されていません",
|
||||||
|
"scriptsDisabledWhileRunning": "プロセス実行中はスクリプトを実行できません"
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"branch": {
|
"branch": {
|
||||||
|
|||||||
@@ -183,7 +183,12 @@
|
|||||||
"unqueuing": "Unqueuing…",
|
"unqueuing": "Unqueuing…",
|
||||||
"queue": "대기열에 추가",
|
"queue": "대기열에 추가",
|
||||||
"cancelQueue": "대기열 취소",
|
"cancelQueue": "대기열 취소",
|
||||||
"queuedMessage": "메시지가 대기열에 추가됨 - 현재 실행이 완료되면 실행됩니다"
|
"queuedMessage": "메시지가 대기열에 추가됨 - 현재 실행이 완료되면 실행됩니다",
|
||||||
|
"runSetupScript": "설정 스크립트 실행",
|
||||||
|
"runCleanupScript": "정리 스크립트 실행",
|
||||||
|
"noSetupScript": "이 프로젝트에 설정 스크립트가 구성되어 있지 않습니다",
|
||||||
|
"noCleanupScript": "이 프로젝트에 정리 스크립트가 구성되어 있지 않습니다",
|
||||||
|
"scriptsDisabledWhileRunning": "프로세스가 실행 중일 때는 스크립트를 실행할 수 없습니다"
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"branch": {
|
"branch": {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
RunAgentSetupRequest,
|
RunAgentSetupRequest,
|
||||||
RunAgentSetupResponse,
|
RunAgentSetupResponse,
|
||||||
GhCliSetupError,
|
GhCliSetupError,
|
||||||
|
RunScriptError,
|
||||||
StatusResponse,
|
StatusResponse,
|
||||||
ListOrganizationsResponse,
|
ListOrganizationsResponse,
|
||||||
OrganizationMemberWithProfile,
|
OrganizationMemberWithProfile,
|
||||||
@@ -605,6 +606,34 @@ export const attemptsApi = {
|
|||||||
);
|
);
|
||||||
return handleApiResponse<ExecutionProcess, GhCliSetupError>(response);
|
return handleApiResponse<ExecutionProcess, GhCliSetupError>(response);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
runSetupScript: async (
|
||||||
|
attemptId: string
|
||||||
|
): Promise<Result<ExecutionProcess, RunScriptError>> => {
|
||||||
|
const response = await makeRequest(
|
||||||
|
`/api/task-attempts/${attemptId}/run-setup-script`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return handleApiResponseAsResult<ExecutionProcess, RunScriptError>(
|
||||||
|
response
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
runCleanupScript: async (
|
||||||
|
attemptId: string
|
||||||
|
): Promise<Result<ExecutionProcess, RunScriptError>> => {
|
||||||
|
const response = await makeRequest(
|
||||||
|
`/api/task-attempts/${attemptId}/run-cleanup-script`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return handleApiResponseAsResult<ExecutionProcess, RunScriptError>(
|
||||||
|
response
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extra helpers
|
// Extra helpers
|
||||||
|
|||||||
@@ -256,6 +256,8 @@ conflict_op: ConflictOp | null,
|
|||||||
*/
|
*/
|
||||||
conflicted_files: Array<string>, };
|
conflicted_files: Array<string>, };
|
||||||
|
|
||||||
|
export type RunScriptError = { "type": "no_script_configured" } | { "type": "process_already_running" };
|
||||||
|
|
||||||
export type DirectoryEntry = { name: string, path: string, is_directory: boolean, is_git_repo: boolean, last_modified: bigint | null, };
|
export type DirectoryEntry = { name: string, path: string, is_directory: boolean, is_git_repo: boolean, last_modified: bigint | null, };
|
||||||
|
|
||||||
export type DirectoryListResponse = { entries: Array<DirectoryEntry>, current_path: string, };
|
export type DirectoryListResponse = { entries: Array<DirectoryEntry>, current_path: string, };
|
||||||
|
|||||||
Reference in New Issue
Block a user