agentic-devtools 0.2.152__py3-none-any.whl → 0.2.154__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.152'
22
- __version_tuple__ = version_tuple = (0, 2, 152)
21
+ __version__ = version = '0.2.154'
22
+ __version_tuple__ = version_tuple = (0, 2, 154)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -49,11 +49,20 @@ _STATE_KEY = "copilot"
49
49
  _TRIGGERED_RUNS_KEY = "auto_start_triggered_runs"
50
50
  _MODEL_KEY = "model_id"
51
51
 
52
- # Retry constants for transient Windows file-lock errors (WinError 32).
52
+ # Retry constants for transient Windows file-lock errors (WinError 32) and
53
+ # rapid non-zero exit codes (batch wrapper fails because copilot.ps1 is locked).
53
54
  _RETRY_MAX_ATTEMPTS = 5 # retries (6 total tries)
54
55
  _RETRY_INITIAL_DELAY_S = 0.5 # first backoff delay in seconds
55
56
  _RETRY_BACKOFF_FACTOR = 2.0 # doubling
56
57
  _RETRY_MAX_DELAY_S = 4.0 # cap per delay in seconds
58
+ _RAPID_FAILURE_THRESHOLD_S = 5.0 # exit within this = transient failure
59
+
60
+ # Cleanup retry constants for transient Windows delete/write locks.
61
+ _CLEANUP_RETRYABLE_WINERRORS = {5, 32, 110}
62
+ _CLEANUP_MAX_RETRIES = 3
63
+ _CLEANUP_INITIAL_DELAY_S = 0.1
64
+ _CLEANUP_BACKOFF_FACTOR = 2.0
65
+ _CLEANUP_MAX_DELAY_S = 0.8
57
66
 
58
67
 
59
68
  def _read_model_from_state(state_file_path: Path) -> str | None:
@@ -200,14 +209,35 @@ def _is_retryable_win_error(exc: OSError) -> bool:
200
209
  return getattr(exc, "winerror", None) == 32
201
210
 
202
211
 
212
+ def _is_retryable_cleanup_win_error(exc: OSError) -> bool:
213
+ """Return ``True`` for transient Windows cleanup lock errors.
214
+
215
+ Covers common transient lock/access-denied cleanup failures:
216
+ - 5 (``ERROR_ACCESS_DENIED``)
217
+ - 32 (``ERROR_SHARING_VIOLATION``)
218
+ - 110 (``ERROR_OPEN_FAILED``)
219
+ """
220
+ return getattr(exc, "winerror", None) in _CLEANUP_RETRYABLE_WINERRORS
221
+
222
+
203
223
  def _run_copilot_with_retry(
204
224
  copilot_args: list[str],
205
225
  cwd: str,
206
226
  ) -> subprocess.CompletedProcess:
207
227
  """Run the Copilot CLI with retry logic for transient Windows file locks.
208
228
 
209
- Wraps ``subprocess.run`` with exponential backoff retries specifically for
210
- ``OSError`` with ``winerror == 32`` (Windows ``ERROR_SHARING_VIOLATION``).
229
+ Wraps ``subprocess.run`` with exponential backoff retries for two failure
230
+ modes:
231
+
232
+ 1. ``OSError`` with ``winerror == 32`` (Windows ``ERROR_SHARING_VIOLATION``)
233
+ raised directly by ``subprocess.run`` when the binary itself is locked.
234
+ 2. Rapid non-zero exit codes from Windows batch wrappers (process exits within
235
+ ``_RAPID_FAILURE_THRESHOLD_S`` seconds). This handles the case where
236
+ a ``.bat`` wrapper starts successfully but the underlying script
237
+ (``copilot.ps1``) is locked by another process (e.g., the VS Code
238
+ Copilot Chat extension during activation). The batch exits non-zero
239
+ almost immediately, which is distinguishable from a legitimate session
240
+ failure (which would take several seconds at minimum).
211
241
 
212
242
  Non-retryable errors (``FileNotFoundError``, other ``OSError`` variants)
213
243
  are re-raised immediately without retry.
@@ -217,7 +247,9 @@ def _run_copilot_with_retry(
217
247
  cwd: Working directory for the subprocess.
218
248
 
219
249
  Returns:
220
- The ``CompletedProcess`` on success.
250
+ The ``CompletedProcess`` result from the final attempt. This may have
251
+ a non-zero ``returncode`` when retries are exhausted for rapid
252
+ non-zero exits from Windows batch wrappers.
221
253
 
222
254
  Raises:
223
255
  FileNotFoundError: If the binary or cwd is missing (not retried).
@@ -232,7 +264,36 @@ def _run_copilot_with_retry(
232
264
 
233
265
  for attempt in range(1, total_tries + 1):
234
266
  try:
235
- return subprocess.run(copilot_args, cwd=cwd) # noqa: S603
267
+ t0 = time.monotonic()
268
+ result = subprocess.run(copilot_args, cwd=cwd) # noqa: S603
269
+ elapsed = time.monotonic() - t0
270
+
271
+ command_name = copilot_args[0].lower() if copilot_args else ""
272
+ is_batch_wrapper = command_name.endswith((".bat", ".cmd"))
273
+ # A rapid non-zero exit is only treated as transient for Windows
274
+ # batch wrappers, where .bat -> .ps1 handoff can fail while
275
+ # copilot.ps1 is temporarily locked by VS Code's extension.
276
+ if result.returncode != 0 and elapsed < _RAPID_FAILURE_THRESHOLD_S and is_batch_wrapper:
277
+ if attempt == total_tries:
278
+ print(
279
+ f"agdt-copilot-auto-start: error: retry budget exhausted "
280
+ f"after {total_tries} attempts (rapid exit code "
281
+ f"{result.returncode} in {elapsed:.1f}s)",
282
+ file=sys.stderr,
283
+ )
284
+ return result
285
+ print(
286
+ f"agdt-copilot-auto-start: warning: copilot exited rapidly "
287
+ f"(code={result.returncode}, {elapsed:.1f}s < {_RAPID_FAILURE_THRESHOLD_S}s, "
288
+ f"attempt {attempt}/{total_tries}), "
289
+ f"retrying in {delay:.1f}s (likely transient batch-wrapper file lock)",
290
+ file=sys.stderr,
291
+ )
292
+ time.sleep(delay)
293
+ delay = min(delay * _RETRY_BACKOFF_FACTOR, _RETRY_MAX_DELAY_S)
294
+ continue
295
+
296
+ return result
236
297
  except FileNotFoundError:
237
298
  raise
238
299
  except OSError as exc:
@@ -272,8 +333,9 @@ def _cleanup_auto_start_task(
272
333
  Delegates to :func:`~agentic_devtools.cli.vscode_tasks.remove_auto_start_task`.
273
334
 
274
335
  Errors are caught so that cleanup failure never changes the caller's exit
275
- code. Transient Windows file-lock errors (``winerror == 32``) emit a
276
- warning to stderr; all other errors are silently ignored.
336
+ code. Transient Windows cleanup lock errors (``winerror`` in ``[5, 32, 110]``)
337
+ are retried up to 3 times with exponential backoff; all failures are
338
+ otherwise silently ignored.
277
339
 
278
340
  Args:
279
341
  worktree_path: Absolute path to the worktree directory.
@@ -288,25 +350,41 @@ def _cleanup_auto_start_task(
288
350
  """
289
351
  vscode_dir = os.path.join(worktree_path, ".vscode")
290
352
  tasks_path = os.path.join(vscode_dir, "tasks.json")
291
- try:
292
- remove_auto_start_task(
293
- tasks_path,
294
- vscode_dir,
295
- task_label,
296
- delete_if_empty=created_new,
297
- )
298
- except OSError as exc:
299
- if _is_retryable_win_error(exc):
353
+ if _CLEANUP_MAX_RETRIES < 0:
354
+ return
355
+ attempts = _CLEANUP_MAX_RETRIES + 1
356
+ delay = _CLEANUP_INITIAL_DELAY_S
357
+ attempt = 1
358
+ while attempt <= attempts: # pragma: no branch
359
+ try:
360
+ remove_auto_start_task(
361
+ tasks_path,
362
+ vscode_dir,
363
+ task_label,
364
+ delete_if_empty=created_new,
365
+ )
366
+ return
367
+ except OSError as exc:
368
+ if not _is_retryable_cleanup_win_error(exc) or attempt == attempts:
369
+ # Best-effort cleanup: any failure must not affect caller exit code.
370
+ return
300
371
  print(
301
372
  f"agdt-copilot-auto-start: warning: cleanup encountered transient "
302
- f"file lock (winerror=32), skipping: {exc}",
373
+ f"file lock (attempt {attempt}/{attempts}, "
374
+ f"winerror={getattr(exc, 'winerror', '?')}), "
375
+ f"retrying in {delay:.1f}s: {exc}",
303
376
  file=sys.stderr,
304
377
  )
305
- # Best-effort cleanup: any failure must not affect the caller's exit code.
306
- except Exception:
307
- # Best-effort cleanup: any failure must be silently ignored so it cannot
308
- # affect the caller's exit code.
309
- pass
378
+ try:
379
+ time.sleep(delay)
380
+ except KeyboardInterrupt:
381
+ return
382
+ delay = min(delay * _CLEANUP_BACKOFF_FACTOR, _CLEANUP_MAX_DELAY_S)
383
+ attempt += 1
384
+ except Exception:
385
+ # Best-effort cleanup: any failure must be silently ignored so it cannot
386
+ # affect the caller's exit code.
387
+ return
310
388
 
311
389
 
312
390
  def copilot_auto_start_cmd(argv: list[str] | None = None) -> None:
@@ -156,6 +156,8 @@ class CopilotSessionResult:
156
156
  when this object is returned).
157
157
  process: The :class:`subprocess.Popen` handle for non-interactive
158
158
  sessions; ``None`` for interactive sessions.
159
+ log_file: Absolute path to the session log file for non-interactive
160
+ sessions; ``None`` for interactive sessions.
159
161
  """
160
162
 
161
163
  session_id: str
@@ -164,6 +166,7 @@ class CopilotSessionResult:
164
166
  start_time: str
165
167
  pid: int | None = field(default=None)
166
168
  process: subprocess.Popen | None = field(default=None, repr=False) # type: ignore[type-arg]
169
+ log_file: str | None = field(default=None)
167
170
 
168
171
 
169
172
  # ---------------------------------------------------------------------------
@@ -175,17 +178,25 @@ def _get_copilot_binary() -> str | None:
175
178
  """Return the path to the copilot binary, or ``None`` if not found.
176
179
 
177
180
  Checks (in order):
178
- 1. The standalone ``copilot`` binary on the system ``PATH``.
179
- 2. The managed install at ``~/.agdt/bin/copilot[.exe]``.
181
+ 1. The managed install at ``~/.agdt/bin/copilot[.exe]``.
182
+ 2. The standalone ``copilot`` binary on the system ``PATH``.
183
+
184
+ The managed install is preferred because it is a direct executable that
185
+ avoids the ``.bat`` → ``copilot.ps1`` indirection used by the VS Code
186
+ Copilot Chat extension's bundled CLI. On Windows, that ``.ps1`` file can
187
+ be temporarily locked by the extension during activation, causing transient
188
+ ``ERROR_SHARING_VIOLATION`` failures that are invisible to
189
+ ``subprocess.run`` (they manifest as non-zero exit codes from the batch
190
+ wrapper rather than ``OSError``).
180
191
 
181
192
  Returns:
182
193
  Absolute path string when found, ``None`` otherwise.
183
194
  """
195
+ if _MANAGED_COPILOT.is_file():
196
+ return str(_MANAGED_COPILOT)
184
197
  system_path = shutil.which("copilot")
185
198
  if system_path:
186
199
  return system_path
187
- if _MANAGED_COPILOT.is_file():
188
- return str(_MANAGED_COPILOT)
189
200
  return None
190
201
 
191
202
 
@@ -921,6 +932,7 @@ def start_copilot_session(
921
932
  start_time=start_time,
922
933
  pid=process.pid,
923
934
  process=process,
935
+ log_file=str(log_file_path),
924
936
  )
925
937
 
926
938
  _persist_session_state(result, model=model)
@@ -394,6 +394,17 @@ Examples:
394
394
  clear_state_for_workflow_initiation(preserve_run_id=bool(skip_copilot_session))
395
395
  set_value("copilot.model_id", model)
396
396
 
397
+ # Remove any stale VS Code auto-start task from a previous workflow to
398
+ # prevent a duplicate Copilot session from firing on folderOpen/reload.
399
+ # This is belt-and-suspenders alongside the cleanup in
400
+ # _start_copilot_session_for_workflow — the earlier we remove the stale
401
+ # task, the smaller the window for a race where VS Code reloads and fires
402
+ # the old task before we start the new session.
403
+ repo_root_early = get_git_repo_root() or os.getcwd()
404
+ from .worktree_setup import _cleanup_stale_auto_start_task_for_worktree
405
+
406
+ _cleanup_stale_auto_start_task_for_worktree(repo_root_early)
407
+
397
408
  # Set provided values in state using the NORMALIZED identifiers. When both issue_key
398
409
  # and pull_request_id are provided, write jira.issue_key FIRST so that the engine-side
399
410
  # priority guard in set_value() (which checks the loaded state dict) sees the issue key
@@ -14,6 +14,7 @@ import platform
14
14
  import shutil
15
15
  import subprocess
16
16
  import sys
17
+ import threading
17
18
  from dataclasses import dataclass
18
19
  from pathlib import Path
19
20
 
@@ -1455,6 +1456,27 @@ def _remove_stale_auto_start_task(
1455
1456
  remove_auto_start_task(tasks_path, vscode_dir, task_label, delete_if_empty=delete_if_empty)
1456
1457
 
1457
1458
 
1459
+ def _cleanup_stale_auto_start_task_for_worktree(worktree_path: str) -> None:
1460
+ """Remove any stale auto-start task from a worktree's ``.vscode/tasks.json``.
1461
+
1462
+ Best-effort helper that derives the paths from *worktree_path* and
1463
+ delegates to :func:`_remove_stale_auto_start_task`. Called before
1464
+ starting a new Copilot session to prevent a leftover ``runOn: folderOpen``
1465
+ task from a previous workflow from spawning a duplicate session when
1466
+ VS Code reloads.
1467
+
1468
+ All errors are silently caught so this never prevents the caller from
1469
+ proceeding.
1470
+ """
1471
+ try:
1472
+ vscode_dir = os.path.join(worktree_path, ".vscode")
1473
+ tasks_path = os.path.join(vscode_dir, "tasks.json")
1474
+ if os.path.isfile(tasks_path):
1475
+ _remove_stale_auto_start_task(tasks_path, vscode_dir, _AUTO_START_TASK_LABEL)
1476
+ except Exception:
1477
+ pass
1478
+
1479
+
1458
1480
  class WorktreeStateContext:
1459
1481
  """Context manager for cross-worktree state resolution.
1460
1482
 
@@ -2347,18 +2369,26 @@ def _start_copilot_session_for_workflow(
2347
2369
  # where stdin/stdout are redirected to DEVNULL/log files).
2348
2370
  has_tty = getattr(sys.stdin, "isatty", lambda: False)() and getattr(sys.stdout, "isatty", lambda: False)()
2349
2371
 
2350
- # --- Auto-start injection confirmed: skip background fallback ------------
2372
+ # --- Auto-start injection confirmed: delayed verification fallback ------
2351
2373
  # When the caller confirms the auto-start task was successfully injected
2352
- # into tasks.json, trust that VS Code will execute it (possibly after a
2353
- # trust/permission dialog). Do NOT start a duplicate background session.
2354
- # The previous 15-second timeout was insufficient for scenarios where
2355
- # VS Code shows a workspace trust or task permission dialog before
2356
- # executing the runOn:folderOpen task.
2374
+ # into tasks.json, we trust that VS Code *will probably* execute it.
2375
+ # However, the task can still fail (e.g., copilot binary locked, permission
2376
+ # dialog dismissed, VS Code crash). Instead of blindly returning True,
2377
+ # we spawn a non-daemon thread that waits for the run ID to appear in state
2378
+ # (proving the task ran). If it doesn't appear within the grace period,
2379
+ # the thread starts a non-interactive background session as a safety net.
2357
2380
  if autostart_injected and not has_tty:
2358
2381
  print(
2359
2382
  "\n--- VS Code auto-start task was successfully injected. "
2360
- "Trusting VS Code to handle the Copilot session "
2361
- "(no background fallback will be started). ---"
2383
+ f"A delayed verification (up to {_AUTOSTART_VERIFICATION_DELAY_S:.0f}s) "
2384
+ "will confirm it ran and may keep this task running; if not, a "
2385
+ "background fallback session will be started automatically. ---"
2386
+ )
2387
+ _spawn_delayed_autostart_verification(
2388
+ worktree_path=worktree_path,
2389
+ start_prompt=start_prompt,
2390
+ workflow_name=workflow_name,
2391
+ model=model,
2362
2392
  )
2363
2393
  return True
2364
2394
 
@@ -2460,6 +2490,12 @@ def _start_copilot_session_for_workflow(
2460
2490
 
2461
2491
  effective_interactive = interactive and is_vscode_available() and has_tty
2462
2492
 
2493
+ # Remove any stale auto-start task from tasks.json BEFORE starting a new
2494
+ # session. A leftover runOn:folderOpen task from a previous workflow
2495
+ # invocation can fire when VS Code reloads, causing a duplicate Copilot
2496
+ # session alongside the one we are about to start. See #1742.
2497
+ _cleanup_stale_auto_start_task_for_worktree(worktree_path)
2498
+
2463
2499
  print(
2464
2500
  f"\n--- Starting gh copilot session for {workflow_name} "
2465
2501
  f"(mode: {'interactive' if effective_interactive else 'non-interactive'}) ---"
@@ -2475,12 +2511,24 @@ def _start_copilot_session_for_workflow(
2475
2511
 
2476
2512
  state_dir = get_state_dir()
2477
2513
  os.environ["AGENTIC_DEVTOOLS_STATE_DIR"] = str(state_dir)
2478
- start_copilot_session(
2514
+ session_result = start_copilot_session(
2479
2515
  prompt=start_prompt,
2480
2516
  working_directory=worktree_path,
2481
2517
  interactive=effective_interactive,
2482
2518
  model=model,
2483
2519
  )
2520
+ # Open the log file in VS Code for non-interactive sessions so the
2521
+ # user can watch Copilot output in real time. Skip in CI (no VS Code
2522
+ # or no TTY on the *original* caller — here we check is_vscode_available
2523
+ # which is False in headless CI).
2524
+ if (
2525
+ not effective_interactive
2526
+ and session_result is not None
2527
+ and session_result.log_file
2528
+ and is_vscode_available()
2529
+ and not _in_test_environment()
2530
+ ):
2531
+ _open_log_in_vscode(session_result.log_file, worktree_path)
2484
2532
  return True
2485
2533
 
2486
2534
 
@@ -2758,6 +2806,128 @@ def _start_copilot_session_for_update_jira_issue(
2758
2806
  _PENDING_AUTO_START_FILENAME = "pending-auto-start.json"
2759
2807
  _FOCUS_SETTLE_SECONDS: float = 2.0
2760
2808
 
2809
+ # Grace period (seconds) for the delayed auto-start verification.
2810
+ # VS Code may show a workspace trust or task permission dialog before
2811
+ # executing runOn:folderOpen, so the window must be generous.
2812
+ _AUTOSTART_VERIFICATION_DELAY_S: float = 45.0
2813
+ _AUTOSTART_VERIFICATION_POLL_S: float = 3.0
2814
+
2815
+
2816
+ def _spawn_delayed_autostart_verification(
2817
+ worktree_path: str,
2818
+ start_prompt: str,
2819
+ workflow_name: str,
2820
+ model: str | None = None,
2821
+ ) -> None:
2822
+ """Spawn a non-daemon thread that verifies the VS Code auto-start task ran.
2823
+
2824
+ After a grace period, the thread checks whether the auto-start task
2825
+ wrote its run ID into ``copilot.auto_start_triggered_runs``. If not,
2826
+ it starts a non-interactive background Copilot session as a fallback
2827
+ and opens the log file in VS Code (when available).
2828
+
2829
+ The thread is non-daemon so it keeps the parent process alive until
2830
+ verification completes (at most ``_AUTOSTART_VERIFICATION_DELAY_S``
2831
+ seconds). If a fallback session is started, the session's non-daemon
2832
+ tee-thread keeps the process alive for the duration of the Copilot
2833
+ subprocess.
2834
+
2835
+ **Safety guard**: This function is a no-op inside test environments
2836
+ (``PYTEST_CURRENT_TEST`` set) to prevent threads from outliving test
2837
+ cases and accidentally spawning real Copilot sessions.
2838
+ """
2839
+ if _in_test_environment():
2840
+ return
2841
+
2842
+ state_file_path, state_run_id = _resolve_state_context_in_worktree(worktree_path, include_run_id=True)
2843
+
2844
+ def _verify_and_fallback() -> None:
2845
+ import time
2846
+
2847
+ from agentic_devtools.cli.copilot.auto_start import _is_run_triggered
2848
+
2849
+ # Prefer the run_id from state when available; fall back to the pending marker written during injection.
2850
+ current_run_id = state_run_id.strip()
2851
+ if not current_run_id:
2852
+ marker_path = os.path.join(worktree_path, ".vscode", _PENDING_AUTO_START_FILENAME)
2853
+ try:
2854
+ if os.path.isfile(marker_path):
2855
+ with open(marker_path, encoding="utf-8") as fh:
2856
+ marker = json.load(fh)
2857
+ if isinstance(marker, dict):
2858
+ run_id_value = marker.get("run_id")
2859
+ if isinstance(run_id_value, str):
2860
+ current_run_id = run_id_value.strip()
2861
+ except (OSError, json.JSONDecodeError, ValueError):
2862
+ current_run_id = ""
2863
+
2864
+ if state_file_path is None or not current_run_id:
2865
+ # Cannot verify — fall through to start fallback immediately
2866
+ print(
2867
+ "\n--- Delayed verification: no state file or run ID available. "
2868
+ "Starting background fallback Copilot session. ---",
2869
+ file=sys.stderr,
2870
+ )
2871
+ else:
2872
+ # Poll for the run ID until the grace period expires.
2873
+ elapsed = 0.0
2874
+ while elapsed < _AUTOSTART_VERIFICATION_DELAY_S:
2875
+ if _is_run_triggered(state_file_path, current_run_id):
2876
+ # Auto-start task confirmed running — no fallback needed.
2877
+ return
2878
+ time.sleep(_AUTOSTART_VERIFICATION_POLL_S)
2879
+ elapsed += _AUTOSTART_VERIFICATION_POLL_S
2880
+
2881
+ print(
2882
+ f"\n--- Delayed verification: VS Code auto-start task did NOT run "
2883
+ f"within {_AUTOSTART_VERIFICATION_DELAY_S:.0f}s. "
2884
+ f"Starting background fallback Copilot session for {workflow_name}. ---",
2885
+ file=sys.stderr,
2886
+ )
2887
+
2888
+ # Start the fallback non-interactive session.
2889
+ try:
2890
+ from ..copilot.session import start_copilot_session
2891
+
2892
+ # Pin state writes to the target worktree state dir when available.
2893
+ target_state_dir = os.path.dirname(state_file_path) if state_file_path else None
2894
+ previous_state_dir = os.environ.get("AGENTIC_DEVTOOLS_STATE_DIR")
2895
+ if target_state_dir:
2896
+ os.environ["AGENTIC_DEVTOOLS_STATE_DIR"] = target_state_dir
2897
+ try:
2898
+ session_result = start_copilot_session(
2899
+ prompt=start_prompt,
2900
+ working_directory=worktree_path,
2901
+ interactive=False,
2902
+ model=model,
2903
+ )
2904
+ finally:
2905
+ if target_state_dir:
2906
+ if previous_state_dir is None:
2907
+ os.environ.pop("AGENTIC_DEVTOOLS_STATE_DIR", None)
2908
+ else:
2909
+ os.environ["AGENTIC_DEVTOOLS_STATE_DIR"] = previous_state_dir
2910
+ # Open log in VS Code so the user can monitor.
2911
+ if (
2912
+ session_result is not None
2913
+ and session_result.log_file
2914
+ and is_vscode_available()
2915
+ and not _in_test_environment()
2916
+ ):
2917
+ _open_log_in_vscode(session_result.log_file, worktree_path)
2918
+ except Exception as exc:
2919
+ print(
2920
+ f"Warning: delayed fallback Copilot session failed: {exc}",
2921
+ file=sys.stderr,
2922
+ )
2923
+
2924
+ thread = threading.Thread(
2925
+ target=_verify_and_fallback,
2926
+ name="autostart-verification",
2927
+ daemon=False,
2928
+ )
2929
+ thread.start()
2930
+
2761
2931
 
2762
2932
  def _write_pending_auto_start_marker(
2763
2933
  worktree_path: str,
@@ -2847,6 +3017,49 @@ def _focus_vscode_window(worktree_path: str) -> bool:
2847
3017
  return True
2848
3018
 
2849
3019
 
3020
+ def _open_log_in_vscode(log_file_path: str, worktree_path: str) -> None:
3021
+ """Open the Copilot session log file in VS Code for live monitoring.
3022
+
3023
+ Uses ``code <log_file_path>`` to request opening the file in VS Code.
3024
+ The CLI decides which window receives the file (often the most recently
3025
+ active window). Best-effort: errors are printed to stderr but never raised.
3026
+
3027
+ Args:
3028
+ log_file_path: Absolute path to the ``.log`` file.
3029
+ worktree_path: The worktree root (used for context in messages).
3030
+ """
3031
+ try:
3032
+ log_file_path = os.path.abspath(log_file_path)
3033
+ use_shell = platform.system() == "Windows"
3034
+ if use_shell and any(ch in log_file_path for ch in "&|<>^%"):
3035
+ print(
3036
+ "Warning: refusing to open log file in VS Code on Windows because "
3037
+ f"path contains cmd.exe metacharacters: {log_file_path!r}",
3038
+ file=sys.stderr,
3039
+ )
3040
+ return
3041
+ proc = subprocess.run(
3042
+ ["code", log_file_path],
3043
+ stdout=subprocess.DEVNULL,
3044
+ stderr=subprocess.DEVNULL,
3045
+ timeout=10,
3046
+ shell=use_shell,
3047
+ )
3048
+ if proc.returncode == 0:
3049
+ print(f"--- Opened Copilot session log in VS Code: {log_file_path} (worktree: {worktree_path}) ---")
3050
+ else:
3051
+ print(
3052
+ f"Warning: 'code' exited with {proc.returncode} when opening log file "
3053
+ f"{log_file_path!r} (worktree: {worktree_path}).",
3054
+ file=sys.stderr,
3055
+ )
3056
+ except (OSError, subprocess.TimeoutExpired) as exc:
3057
+ print(
3058
+ f"Warning: could not open log file in VS Code: {exc} (worktree: {worktree_path}, file: {log_file_path!r})",
3059
+ file=sys.stderr,
3060
+ )
3061
+
3062
+
2850
3063
  def _try_terminal_send_fallback(worktree_path: str, expected_run_id: str | None = None) -> bool:
2851
3064
  """Attempt to start the Copilot session via VS Code terminal sendSequence.
2852
3065
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-devtools
3
- Version: 0.2.152
3
+ Version: 0.2.154
4
4
  Summary: Agentic devtools integrate Jira, DevOps & more
5
5
  Author: ayaiayorg
6
6
  License-Expression: MIT
@@ -1,5 +1,5 @@
1
1
  agentic_devtools/__init__.py,sha256=J_Zw_vWKghk-cLmqI83hXQmSiS8zMhGIHM5WPLDkZuo,242
2
- agentic_devtools/_version.py,sha256=QR6RgQM2XLjvmCHvQe6iRYZ8ysRf1j2G_xGWowWyMu8,524
2
+ agentic_devtools/_version.py,sha256=a3-860ghbqgShvATariWvr3uQTtn4SgO05rdw4MFYhE,524
3
3
  agentic_devtools/agdt_gitignore.py,sha256=Jcc9BUiCIqCG9tUuHc5Jua5tPT_QtTnEK6Gr0v_RrKQ,1559
4
4
  agentic_devtools/background_tasks.py,sha256=QCiCSTcU8HSFa_g_fa2NWXhhRZU4zo6YgsPSIVVmdOU,16971
5
5
  agentic_devtools/config.py,sha256=X6zv8Twviv9LMiV7TYy_6SQeNezJHV8lBNvIxLkOqLY,8049
@@ -145,8 +145,8 @@ agentic_devtools/cli/ci/tracker/renderer.py,sha256=6LhId0Ehfcmn_BAtrH69z9Cl4xBfQ
145
145
  agentic_devtools/cli/config/__init__.py,sha256=1KFPaXnhgP1ZBvIxxLAethaCeY5RzTM2GwU-xnt69ro,271
146
146
  agentic_devtools/cli/config/project_config.py,sha256=NjFW3Ds0vUxotGSJwp7Z4zktFItTvRp2pQ0PDdWwYIE,3193
147
147
  agentic_devtools/cli/copilot/__init__.py,sha256=FFlmM67FbXNTKvWDm4ySOUXL6DvZMnyMWNrGa_ALiq4,562
148
- agentic_devtools/cli/copilot/auto_start.py,sha256=FATVY0_IDrcTSndEtjchKOZB_hJMlG0pRvBIj9HO7yU,29763
149
- agentic_devtools/cli/copilot/session.py,sha256=q35FPVOlADYjSTtFpW0aHIDAOzQxqlVrUroo731rrbM,39112
148
+ agentic_devtools/cli/copilot/auto_start.py,sha256=0T3tIFw3R18_KPZ-00i9HsyG5Z1dccHZmLXqYEiB8xM,33522
149
+ agentic_devtools/cli/copilot/session.py,sha256=7v6Wx24Edf3pAkEu4eFbxrp_vIJFAQ4xLPZq73m_ghI,39821
150
150
  agentic_devtools/cli/git/__init__.py,sha256=ACoWYDgm8AMjk2wJ_Ycc5CY7xCXJQtfB55HzM-yx8WA,2538
151
151
  agentic_devtools/cli/git/agdt_branch.py,sha256=4qHH1dxYr3LraAB2Q6_fjgLrI3KrPCvXu-uOsJryN7M,37373
152
152
  agentic_devtools/cli/git/async_commands.py,sha256=DdvoMsqcVh5T78wXoFQO2m1B61QC-QPDsy6bwZ9VBDk,9883
@@ -258,11 +258,11 @@ agentic_devtools/cli/workflows/advancement.py,sha256=bxrF4L0r6LTu9Di6Ll6PUEFLAYA
258
258
  agentic_devtools/cli/workflows/applied_suggestions.py,sha256=vRDYk5nokfMtmiQzCanBTBmRRFWIPNZILJDQ4hIb7nE,9306
259
259
  agentic_devtools/cli/workflows/base.py,sha256=pgdzP5M3CKXTg1mVMqUz2t-C_NMCKbptmiM-iUgbsBg,10966
260
260
  agentic_devtools/cli/workflows/checklist.py,sha256=3JMY-pvqb6wUpG2XXSFNnYiGYO9aM9O96Z6GW-4qb_s,9998
261
- agentic_devtools/cli/workflows/commands.py,sha256=qSxjCl4sn3jRvR7I8MB5QS-wOOZxUU-mUQtdKnChWhc,142548
261
+ agentic_devtools/cli/workflows/commands.py,sha256=Bto2FM7oFTKlYkP2mT0o20ARS9R4ivPD_mOOccRDk1E,143166
262
262
  agentic_devtools/cli/workflows/engine_resolution.py,sha256=HUYMGERXCyJJgc0RgWg5w7JdpHGPjdXfnPkZAqMn5iI,2309
263
263
  agentic_devtools/cli/workflows/manager.py,sha256=BxSDix8IV6QQhawIMvfIN0iOKbkFFxAauwONlkse9uU,28721
264
264
  agentic_devtools/cli/workflows/preflight.py,sha256=vljErnXSvtgbN9m5zArGjZ4iemk6LajPj2mqGkyqGbs,12233
265
- agentic_devtools/cli/workflows/worktree_setup.py,sha256=m7FV-gFjest05WRwHTY1jviclpxeMF_w7hxn7XSC24Q,156708
265
+ agentic_devtools/cli/workflows/worktree_setup.py,sha256=gpGwpqUlR41JvEiUNZsIzZ2dP1KV0pdh18Hdc-Ub75k,166019
266
266
  agentic_devtools/context/__init__.py,sha256=Rrk10DQHFnkFiFd1w9EFwBPo36tV-HJqjgaANm_3mg0,427
267
267
  agentic_devtools/context/models.py,sha256=tsKM6x_yAHkWcJOzNrgMaqkXyDxR1mVZyC5Dbu_rIdE,1518
268
268
  agentic_devtools/context/nodes.py,sha256=cexQ92RbPddTWV8zwLTK4EPEfVGZZk4Sp-nzblgE3wo,4951
@@ -600,8 +600,8 @@ agentic_devtools/_bundled_skills/prompts/speckit.plan.prompt.md,sha256=IJja5r2Sd
600
600
  agentic_devtools/_bundled_skills/prompts/speckit.specify.prompt.md,sha256=eyzE3GRi2hyW30a6xPYOU7q6MJf0skrD-baEGURYqpg,31
601
601
  agentic_devtools/_bundled_skills/prompts/speckit.tasks.prompt.md,sha256=iPxXwon5nV6dNcJV8-JoP3PssKUVXctNiG-C9SsRhB8,29
602
602
  agentic_devtools/_bundled_skills/prompts/speckit.taskstoissues.prompt.md,sha256=L5Y21PMSoUcPAAdHy2Jnf-wGVdi04jV_pPvyOJZfpm0,37
603
- agentic_devtools-0.2.152.dist-info/METADATA,sha256=2nIhlupv7NmOkNC5dRNiAJq115Rmo1FbZnOHWDkIQa8,29269
604
- agentic_devtools-0.2.152.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
605
- agentic_devtools-0.2.152.dist-info/entry_points.txt,sha256=CDSdq-zuDsv3AHT2rcY6EBRdtL1tsV_Q3shNW1AP7Ec,9329
606
- agentic_devtools-0.2.152.dist-info/licenses/LICENSE,sha256=yBEDdICksxhBYLWoERKp9MTqwGnUF6Ryj9BTLwXTc6k,1082
607
- agentic_devtools-0.2.152.dist-info/RECORD,,
603
+ agentic_devtools-0.2.154.dist-info/METADATA,sha256=wE2Q9F0IF2bwtMB211fHD4BN9T6hpTFu14bPxXvygXY,29269
604
+ agentic_devtools-0.2.154.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
605
+ agentic_devtools-0.2.154.dist-info/entry_points.txt,sha256=CDSdq-zuDsv3AHT2rcY6EBRdtL1tsV_Q3shNW1AP7Ec,9329
606
+ agentic_devtools-0.2.154.dist-info/licenses/LICENSE,sha256=yBEDdICksxhBYLWoERKp9MTqwGnUF6Ryj9BTLwXTc6k,1082
607
+ agentic_devtools-0.2.154.dist-info/RECORD,,