kin: KIN-ARCH-023-debugger
This commit is contained in:
parent
f1c868e335
commit
2be94f0c68
4 changed files with 94 additions and 68 deletions
149
cli/main.py
149
cli/main.py
|
|
@ -616,83 +616,98 @@ def run_task(ctx, task_id, dry_run, allow_write):
|
|||
is_noninteractive = os.environ.get("KIN_NONINTERACTIVE") == "1"
|
||||
click.echo(f"Task: {task['id']} — {task['title']}")
|
||||
|
||||
# Step 1: PM decomposes
|
||||
click.echo("Running PM to decompose task...")
|
||||
pm_started_at = datetime.now(timezone.utc).isoformat()
|
||||
pm_result = run_agent(
|
||||
conn, "pm", task_id, project_id,
|
||||
model="sonnet", dry_run=dry_run,
|
||||
allow_write=allow_write, noninteractive=is_noninteractive,
|
||||
)
|
||||
pm_ended_at = datetime.now(timezone.utc).isoformat()
|
||||
# Step 1a: Check for pre-built steps from revise endpoint (e.g. with analyst injection).
|
||||
# Decision #866: steps built in web/api.py are saved as pending_steps and consumed here.
|
||||
pending_steps = task.get("pending_steps")
|
||||
if pending_steps:
|
||||
pipeline_steps = pending_steps
|
||||
models.update_task(conn, task_id, pending_steps=None)
|
||||
click.echo(f"Using pre-built pipeline ({len(pipeline_steps)} steps, skipping PM)...")
|
||||
pm_result = None
|
||||
pm_started_at = pm_ended_at = None
|
||||
if is_noninteractive:
|
||||
click.echo("\n[non-interactive] Auto-executing pipeline...")
|
||||
elif not click.confirm("\nExecute pipeline?"):
|
||||
click.echo("Aborted.")
|
||||
return
|
||||
else:
|
||||
# Step 1b: PM decomposes
|
||||
click.echo("Running PM to decompose task...")
|
||||
pm_started_at = datetime.now(timezone.utc).isoformat()
|
||||
pm_result = run_agent(
|
||||
conn, "pm", task_id, project_id,
|
||||
model="sonnet", dry_run=dry_run,
|
||||
allow_write=allow_write, noninteractive=is_noninteractive,
|
||||
)
|
||||
pm_ended_at = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
if dry_run:
|
||||
click.echo("\n--- PM Prompt (dry-run) ---")
|
||||
click.echo(pm_result.get("prompt", "")[:2000])
|
||||
click.echo("\n(Dry-run: PM would produce a pipeline JSON)")
|
||||
return
|
||||
if dry_run:
|
||||
click.echo("\n--- PM Prompt (dry-run) ---")
|
||||
click.echo(pm_result.get("prompt", "")[:2000])
|
||||
click.echo("\n(Dry-run: PM would produce a pipeline JSON)")
|
||||
return
|
||||
|
||||
if not pm_result["success"]:
|
||||
click.echo(f"PM failed: {pm_result.get('output', 'unknown error')}", err=True)
|
||||
raise SystemExit(1)
|
||||
|
||||
# Parse PM output for pipeline
|
||||
output = pm_result.get("output")
|
||||
if isinstance(output, str):
|
||||
try:
|
||||
output = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
click.echo(f"PM returned non-JSON output:\n{output[:500]}", err=True)
|
||||
if not pm_result["success"]:
|
||||
click.echo(f"PM failed: {pm_result.get('output', 'unknown error')}", err=True)
|
||||
raise SystemExit(1)
|
||||
|
||||
if not isinstance(output, dict) or "pipeline" not in output:
|
||||
click.echo(f"PM output missing 'pipeline' key:\n{json.dumps(output, indent=2)[:500]}", err=True)
|
||||
raise SystemExit(1)
|
||||
# Parse PM output for pipeline
|
||||
output = pm_result.get("output")
|
||||
if isinstance(output, str):
|
||||
try:
|
||||
output = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
click.echo(f"PM returned non-JSON output:\n{output[:500]}", err=True)
|
||||
raise SystemExit(1)
|
||||
|
||||
pipeline_steps = output["pipeline"]
|
||||
if not isinstance(pipeline_steps, list) or not pipeline_steps:
|
||||
click.echo(
|
||||
f"PM returned empty or invalid pipeline: {pipeline_steps!r}", err=True
|
||||
)
|
||||
raise SystemExit(1)
|
||||
analysis = output.get("analysis", "")
|
||||
if not isinstance(output, dict) or "pipeline" not in output:
|
||||
click.echo(f"PM output missing 'pipeline' key:\n{json.dumps(output, indent=2)[:500]}", err=True)
|
||||
raise SystemExit(1)
|
||||
|
||||
# Save completion_mode from PM output to task (only if neither task nor project has explicit mode)
|
||||
task_current = models.get_task(conn, task_id)
|
||||
update_fields = {}
|
||||
project = models.get_project(conn, project_id)
|
||||
project_mode = project.get("execution_mode") if project else None
|
||||
if not task_current.get("execution_mode") and not project_mode:
|
||||
pm_completion_mode = models.validate_completion_mode(
|
||||
output.get("completion_mode", "review")
|
||||
)
|
||||
update_fields["execution_mode"] = pm_completion_mode
|
||||
import logging
|
||||
logging.getLogger("kin").info(
|
||||
"PM set completion_mode=%s for task %s", pm_completion_mode, task_id
|
||||
)
|
||||
pipeline_steps = output["pipeline"]
|
||||
if not isinstance(pipeline_steps, list) or not pipeline_steps:
|
||||
click.echo(
|
||||
f"PM returned empty or invalid pipeline: {pipeline_steps!r}", err=True
|
||||
)
|
||||
raise SystemExit(1)
|
||||
analysis = output.get("analysis", "")
|
||||
|
||||
# Save category from PM output (only if task has no category yet)
|
||||
if not task_current.get("category"):
|
||||
pm_category = output.get("category")
|
||||
if pm_category and isinstance(pm_category, str):
|
||||
pm_category = pm_category.upper()
|
||||
if pm_category in models.TASK_CATEGORIES:
|
||||
update_fields["category"] = pm_category
|
||||
# Save completion_mode from PM output to task (only if neither task nor project has explicit mode)
|
||||
task_current = models.get_task(conn, task_id)
|
||||
update_fields = {}
|
||||
project = models.get_project(conn, project_id)
|
||||
project_mode = project.get("execution_mode") if project else None
|
||||
if not task_current.get("execution_mode") and not project_mode:
|
||||
pm_completion_mode = models.validate_completion_mode(
|
||||
output.get("completion_mode", "review")
|
||||
)
|
||||
update_fields["execution_mode"] = pm_completion_mode
|
||||
import logging
|
||||
logging.getLogger("kin").info(
|
||||
"PM set completion_mode=%s for task %s", pm_completion_mode, task_id
|
||||
)
|
||||
|
||||
if update_fields:
|
||||
models.update_task(conn, task_id, **update_fields)
|
||||
# Save category from PM output (only if task has no category yet)
|
||||
if not task_current.get("category"):
|
||||
pm_category = output.get("category")
|
||||
if pm_category and isinstance(pm_category, str):
|
||||
pm_category = pm_category.upper()
|
||||
if pm_category in models.TASK_CATEGORIES:
|
||||
update_fields["category"] = pm_category
|
||||
|
||||
click.echo(f"\nAnalysis: {analysis}")
|
||||
click.echo(f"Pipeline ({len(pipeline_steps)} steps):")
|
||||
for i, step in enumerate(pipeline_steps, 1):
|
||||
click.echo(f" {i}. {step['role']} ({step.get('model', 'sonnet')}): {step.get('brief', '')}")
|
||||
if update_fields:
|
||||
models.update_task(conn, task_id, **update_fields)
|
||||
|
||||
if is_noninteractive:
|
||||
click.echo("\n[non-interactive] Auto-executing pipeline...")
|
||||
elif not click.confirm("\nExecute pipeline?"):
|
||||
click.echo("Aborted.")
|
||||
return
|
||||
click.echo(f"\nAnalysis: {analysis}")
|
||||
click.echo(f"Pipeline ({len(pipeline_steps)} steps):")
|
||||
for i, step in enumerate(pipeline_steps, 1):
|
||||
click.echo(f" {i}. {step['role']} ({step.get('model', 'sonnet')}): {step.get('brief', '')}")
|
||||
|
||||
if is_noninteractive:
|
||||
click.echo("\n[non-interactive] Auto-executing pipeline...")
|
||||
elif not click.confirm("\nExecute pipeline?"):
|
||||
click.echo("Aborted.")
|
||||
return
|
||||
|
||||
# Step 2: Execute pipeline
|
||||
click.echo("\nExecuting pipeline...")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue