Files
Aperant/apps/backend/agents/tools_pkg/tools/subtask.py
T
Andy 40fa1dc001 feat(ui): add version 2.7.5 reauthentication warning modal (#1384)
* feat(ui): add one-time version 2.7.5 reauthentication warning modal

Users upgrading to v2.7.5 need to reauthenticate their Claude profile
due to authentication changes. This adds a modal that:
- Shows once on first launch of 2.7.5
- Guides users to Settings > Integrations to reauthenticate
- Persists dismissal state in seenVersionWarnings setting
- Supports EN/FR translations

Also fixes a duplicate test case in rate-limit-detector tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(subprocess): use platform abstraction for auth failure process killing

- Replace direct process.platform checks with isWindows() from platform module
- Use execFile() instead of exec() for Windows taskkill command to avoid
  string interpolation in shell commands (security best practice)
- Remove redundant require('child_process') since exec/execFile already imported
- Add real-time auth failure detection in subprocess stdout/stderr
- Kill subprocess immediately on 401 auth errors to prevent error spam
- Use process groups on Unix (-pid) and taskkill /T on Windows for tree killing

These changes improve code consistency with project cross-platform guidelines
and fix 401 auth detection for Claude API errors during GitHub/GitLab operations.

* feat(auth): enhance authentication failure detection and handling

- Updated rate-limit-detector to recognize additional auth failure patterns, including OAuth token expiration and specific Claude API error messages.
- Integrated auth failure detection into GitHub and GitLab auto-fix handlers, enabling real-time feedback on authentication issues.
- Enhanced PR review and triage handlers to log and communicate auth failures to the renderer.
- Modified AuthFailureModal to direct users to the integrations settings for re-authentication.

These changes improve user experience by providing clearer feedback on authentication issues and streamline the re-authentication process.

* fix: address PR review issues and improve code quality

- Fix killed subprocess incorrectly reported as successful (HIGH)
  - Change exit code handling from `code ?? 0` to `code ?? -1`
  - Add `killedDueToAuthFailure` flag to track auth failure kills
  - Check flag in close handler before checking exitCode

- Fix subprocess not killed if onAuthFailure callback throws (MEDIUM)
  - Wrap onAuthFailure callback in try-catch

- Add missing onAuthFailure callback in checkNewIssues (MEDIUM)
  - Add optional onAuthFailure parameter to checkNewIssues function
  - Pass callback from IPC handler

- Add error handling for getAppVersion() async call (LOW)
  - Wrap in try-catch to prevent unhandled promise rejection

Code quality improvements:
- Extract VERSION_WARNING_275 constant to avoid magic strings
- Create createAuthFailureCallback() helper to reduce duplication
- Use isWindows() consistently instead of process.platform checks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove node_modules symlink and clean up package-lock.json

- Deleted the node_modules symlink to ensure a clean project structure.
- Removed unnecessary "peer" properties from several dependencies in package-lock.json to streamline the file and improve clarity.

* fix(security): replace dangerouslySetInnerHTML with Trans component and persist version warning

- Replace dangerouslySetInnerHTML in OAuthStep.tsx and ClaudeOAuthFlow.tsx with
  react-i18next Trans component to prevent XSS vulnerabilities
- Fix version warning persistence: use saveSettings() instead of updateSettings()
  to persist seenVersionWarnings to disk (not just in-memory)
- Map <code> and <strong> HTML tags in translations to safe React elements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(subprocess): add comprehensive auth failure detection tests

Add 7 new test cases for auth failure handling in subprocess-runner:
- Auth failure detection from stdout
- Auth failure detection from stderr
- Only emit auth failure once (dedupe)
- Process kill on auth failure
- No callback when no auth failure
- Graceful handling of callback errors
- Result error set when killed due to auth failure

Also change console.log to console.warn for taskkill error handling
to improve visibility of error conditions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Alex <63423455+AlexMadera@users.noreply.github.com>
2026-01-21 11:32:19 +01:00

205 lines
6.4 KiB
Python

"""
Subtask Management Tools
========================
Tools for managing subtask status in implementation_plan.json.
"""
import json
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from core.file_utils import write_json_atomic
from spec.validate_pkg.auto_fix import auto_fix_plan
try:
from claude_agent_sdk import tool
SDK_TOOLS_AVAILABLE = True
except ImportError:
SDK_TOOLS_AVAILABLE = False
tool = None
def _update_subtask_in_plan(
plan: dict[str, Any],
subtask_id: str,
status: str,
notes: str,
) -> bool:
"""
Update a subtask in the plan.
Args:
plan: The implementation plan dict
subtask_id: ID of the subtask to update
status: New status (pending, in_progress, completed, failed)
notes: Optional notes to add
Returns:
True if subtask was found and updated, False otherwise
"""
subtask_found = False
for phase in plan.get("phases", []):
for subtask in phase.get("subtasks", []):
if subtask.get("id") == subtask_id:
subtask["status"] = status
if notes:
subtask["notes"] = notes
subtask["updated_at"] = datetime.now(timezone.utc).isoformat()
subtask_found = True
break
if subtask_found:
break
if subtask_found:
plan["last_updated"] = datetime.now(timezone.utc).isoformat()
return subtask_found
def create_subtask_tools(spec_dir: Path, project_dir: Path) -> list:
"""
Create subtask management tools.
Args:
spec_dir: Path to the spec directory
project_dir: Path to the project root
Returns:
List of subtask tool functions
"""
if not SDK_TOOLS_AVAILABLE:
return []
tools = []
# -------------------------------------------------------------------------
# Tool: update_subtask_status
# -------------------------------------------------------------------------
@tool(
"update_subtask_status",
"Update the status of a subtask in implementation_plan.json. Use this when completing or starting a subtask.",
{"subtask_id": str, "status": str, "notes": str},
)
async def update_subtask_status(args: dict[str, Any]) -> dict[str, Any]:
"""Update subtask status in the implementation plan."""
subtask_id = args["subtask_id"]
status = args["status"]
notes = args.get("notes", "")
valid_statuses = ["pending", "in_progress", "completed", "failed"]
if status not in valid_statuses:
return {
"content": [
{
"type": "text",
"text": f"Error: Invalid status '{status}'. Must be one of: {valid_statuses}",
}
]
}
plan_file = spec_dir / "implementation_plan.json"
if not plan_file.exists():
return {
"content": [
{
"type": "text",
"text": "Error: implementation_plan.json not found",
}
]
}
try:
with open(plan_file, encoding="utf-8") as f:
plan = json.load(f)
subtask_found = _update_subtask_in_plan(plan, subtask_id, status, notes)
if not subtask_found:
return {
"content": [
{
"type": "text",
"text": f"Error: Subtask '{subtask_id}' not found in implementation plan",
}
]
}
# Use atomic write to prevent file corruption
write_json_atomic(plan_file, plan, indent=2)
return {
"content": [
{
"type": "text",
"text": f"Successfully updated subtask '{subtask_id}' to status '{status}'",
}
]
}
except json.JSONDecodeError as e:
# Attempt to auto-fix the plan and retry
if auto_fix_plan(spec_dir):
# Retry after fix
try:
with open(plan_file, encoding="utf-8") as f:
plan = json.load(f)
subtask_found = _update_subtask_in_plan(
plan, subtask_id, status, notes
)
if subtask_found:
write_json_atomic(plan_file, plan, indent=2)
return {
"content": [
{
"type": "text",
"text": f"Successfully updated subtask '{subtask_id}' to status '{status}' (after auto-fix)",
}
]
}
else:
return {
"content": [
{
"type": "text",
"text": f"Error: Subtask '{subtask_id}' not found in implementation plan (after auto-fix)",
}
]
}
except Exception as retry_err:
logging.warning(
f"Subtask update retry failed after auto-fix: {retry_err}"
)
return {
"content": [
{
"type": "text",
"text": f"Error: Subtask update failed after auto-fix: {retry_err}",
}
]
}
return {
"content": [
{
"type": "text",
"text": f"Error: Invalid JSON in implementation_plan.json: {e}",
}
]
}
except Exception as e:
return {
"content": [
{"type": "text", "text": f"Error updating subtask status: {e}"}
]
}
tools.append(update_subtask_status)
return tools