|
4 | 4 | a simplified REST API for managing AgenticSessions. |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import json |
7 | 8 | import os |
8 | 9 | import re |
9 | 10 | from datetime import datetime, timedelta |
10 | 11 | from typing import Any |
11 | 12 |
|
12 | 13 | import httpx |
13 | 14 |
|
14 | | -from mcp_acp.settings import load_clusters_config, load_settings |
| 15 | +from mcp_acp.settings import _acpctl_config_path, load_clusters_config, load_settings |
15 | 16 | from utils.pylogger import get_python_logger |
16 | 17 |
|
17 | 18 | logger = get_python_logger() |
18 | 19 |
|
19 | 20 | LABEL_VALUE_PATTERN = re.compile(r"^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$") |
20 | 21 |
|
| 22 | + |
| 23 | +def _read_acpctl_token() -> str | None: |
| 24 | + """Read token from the acpctl CLI config file as a last-resort fallback. |
| 25 | +
|
| 26 | + Used when clusters.yaml exists but a cluster entry has no token. |
| 27 | + """ |
| 28 | + try: |
| 29 | + data = json.loads(_acpctl_config_path().read_text()) |
| 30 | + return data.get("access_token") or None |
| 31 | + except (FileNotFoundError, json.JSONDecodeError, OSError): |
| 32 | + return None |
| 33 | + |
| 34 | + |
21 | 35 | SESSION_TEMPLATES: dict[str, dict[str, Any]] = { |
22 | 36 | "triage": { |
23 | | - "workflow": "triage", |
24 | | - "llmConfig": {"model": "claude-sonnet-4", "temperature": 0.7}, |
| 37 | + "task": "Triage: investigate and classify the issue.", |
| 38 | + "model": "claude-sonnet-4", |
25 | 39 | }, |
26 | 40 | "bugfix": { |
27 | | - "workflow": "bugfix", |
28 | | - "llmConfig": {"model": "claude-sonnet-4", "temperature": 0.3}, |
| 41 | + "task": "Bugfix: diagnose and fix the reported bug.", |
| 42 | + "model": "claude-sonnet-4", |
29 | 43 | }, |
30 | 44 | "feature": { |
31 | | - "workflow": "feature-development", |
32 | | - "llmConfig": {"model": "claude-sonnet-4", "temperature": 0.5}, |
| 45 | + "task": "Feature development: implement the requested feature.", |
| 46 | + "model": "claude-sonnet-4", |
33 | 47 | }, |
34 | 48 | "exploration": { |
35 | | - "workflow": "codebase-exploration", |
36 | | - "llmConfig": {"model": "claude-sonnet-4", "temperature": 0.8}, |
| 49 | + "task": "Codebase exploration: explore and document the codebase.", |
| 50 | + "model": "claude-sonnet-4", |
37 | 51 | }, |
38 | 52 | } |
39 | 53 |
|
@@ -97,12 +111,20 @@ def _get_cluster_config(self, cluster_name: str | None = None) -> dict[str, Any] |
97 | 111 | } |
98 | 112 |
|
99 | 113 | def _get_token(self, cluster_config: dict[str, Any]) -> str: |
100 | | - """Get authentication token for a cluster.""" |
101 | | - token = cluster_config.get("token") or os.getenv("ACP_TOKEN") |
| 114 | + """Get authentication token for a cluster. |
| 115 | +
|
| 116 | + Resolution order: |
| 117 | + 1. Per-cluster token in clusters.yaml |
| 118 | + 2. ACP_TOKEN environment variable |
| 119 | + 3. acpctl CLI config (~/.config/ambient/config.json) |
| 120 | + """ |
| 121 | + token = cluster_config.get("token") or os.getenv("ACP_TOKEN") or _read_acpctl_token() |
102 | 122 |
|
103 | 123 | if not token: |
104 | 124 | raise ValueError( |
105 | | - "No authentication token available. Set 'token' in clusters.yaml or ACP_TOKEN environment variable." |
| 125 | + "No authentication token available. " |
| 126 | + "Run 'acpctl login --token <token> --url <url>', " |
| 127 | + "set 'token' in clusters.yaml, or set ACP_TOKEN environment variable." |
106 | 128 | ) |
107 | 129 |
|
108 | 130 | return token |
@@ -338,26 +360,22 @@ async def create_session( |
338 | 360 | initial_prompt: str, |
339 | 361 | display_name: str | None = None, |
340 | 362 | repos: list[str] | None = None, |
341 | | - interactive: bool = False, |
342 | 363 | model: str = "claude-sonnet-4", |
343 | | - timeout: int = 900, |
344 | 364 | dry_run: bool = False, |
345 | 365 | ) -> dict[str, Any]: |
346 | 366 | """Create an AgenticSession with a custom prompt.""" |
347 | 367 | self._validate_input(project, "project") |
348 | 368 |
|
349 | 369 | session_data: dict[str, Any] = { |
350 | | - "initialPrompt": initial_prompt, |
351 | | - "interactive": interactive, |
352 | | - "llmConfig": {"model": model}, |
353 | | - "timeout": timeout, |
| 370 | + "task": initial_prompt, |
| 371 | + "model": model, |
354 | 372 | } |
355 | 373 |
|
356 | 374 | if display_name: |
357 | 375 | session_data["displayName"] = display_name |
358 | 376 |
|
359 | 377 | if repos: |
360 | | - session_data["repos"] = repos |
| 378 | + session_data["repos"] = [{"url": r} for r in repos] |
361 | 379 |
|
362 | 380 | if dry_run: |
363 | 381 | return { |
@@ -401,7 +419,7 @@ async def create_session_from_template( |
401 | 419 | } |
402 | 420 |
|
403 | 421 | if repos: |
404 | | - session_data["repos"] = repos |
| 422 | + session_data["repos"] = [{"url": r} for r in repos] |
405 | 423 |
|
406 | 424 | if dry_run: |
407 | 425 | return { |
@@ -524,14 +542,10 @@ async def clone_session( |
524 | 542 | source = await self._request("GET", f"/v1/sessions/{source_session}", project) |
525 | 543 |
|
526 | 544 | clone_data: dict[str, Any] = { |
527 | | - "displayName": new_display_name, |
528 | | - "initialPrompt": source.get("initialPrompt", ""), |
529 | | - "interactive": source.get("interactive", False), |
530 | | - "timeout": source.get("timeout", 900), |
| 545 | + "task": source.get("task", ""), |
| 546 | + "model": source.get("model", "claude-sonnet-4"), |
531 | 547 | } |
532 | 548 |
|
533 | | - if source.get("llmConfig"): |
534 | | - clone_data["llmConfig"] = source["llmConfig"] |
535 | 549 | if source.get("repos"): |
536 | 550 | clone_data["repos"] = source["repos"] |
537 | 551 |
|
|
0 commit comments