Add on-demand transport prompt output

This commit is contained in:
jwinterm
2026-07-07 21:59:29 -04:00
parent d832174982
commit 3906964c93
8 changed files with 229 additions and 12 deletions
+10
View File
@@ -168,6 +168,9 @@ def _cmd_open(
message=transport_result.message,
transport_setup_status=transport_result.status,
transport_setup_provider=transport_result.provider,
transport_setup_owned=transport_result.owned,
transport_setup_pid=transport_result.pid,
transport_setup_endpoint=transport_result.endpoint,
transport_setup_message=transport_result.message,
)
open_plan = execute_open(open_plan, root=Path.cwd())
@@ -196,8 +199,15 @@ def _cmd_open(
f"proxy={open_plan.proxy} "
f"runtime_path={runtime_path} "
f"user_js_path={user_js_path} "
f"setup_prompt_title=\"{_safe(open_plan.setup_prompt_title)}\" "
f"setup_prompt_body=\"{_safe(open_plan.setup_prompt_body)}\" "
f"setup_prompt_approve_label=\"{_safe(open_plan.setup_prompt_approve_label)}\" "
f"setup_prompt_approval_command=\"{_safe(open_plan.setup_prompt_approval_command)}\" "
f"transport_setup_status={open_plan.transport_setup_status} "
f"transport_setup_provider={open_plan.transport_setup_provider} "
f"transport_setup_owned={str(open_plan.transport_setup_owned).lower()} "
f"transport_setup_pid={open_plan.transport_setup_pid} "
f"transport_setup_endpoint={open_plan.transport_setup_endpoint} "
f"transport_setup_message=\"{_safe(open_plan.transport_setup_message)}\" "
f"launch_command=\"{_safe(launch_command)}\" "
f"setup_steps=\"{_safe(setup_steps)}\" "
+67
View File
@@ -5,6 +5,7 @@ from dataclasses import replace
import json
import os
from pathlib import Path
import shlex
import subprocess
from urllib.parse import urlparse
@@ -34,8 +35,15 @@ class OpenPlan:
message: str
launch_spec: BrowserLaunchSpec | None = None
browser_pid: int = 0
setup_prompt_title: str = "-"
setup_prompt_body: str = "-"
setup_prompt_approve_label: str = "-"
setup_prompt_approval_command: str = "-"
transport_setup_status: str = "-"
transport_setup_provider: str = "-"
transport_setup_owned: bool = False
transport_setup_pid: int = 0
transport_setup_endpoint: str = "-"
transport_setup_message: str = "-"
@@ -68,6 +76,7 @@ def prepare_open(
if browse_plan.requires_consent:
setup_steps = _setup_steps(browse_plan)
setup_prompt = _setup_prompt(browse_plan, setup_steps)
if not consent:
return OpenPlan(
browse_plan=browse_plan,
@@ -79,6 +88,10 @@ def prepare_open(
setup_steps=setup_steps,
message=browse_plan.prompt,
launch_spec=launch_spec,
setup_prompt_title=setup_prompt.title,
setup_prompt_body=setup_prompt.body,
setup_prompt_approve_label=setup_prompt.approve_label,
setup_prompt_approval_command=setup_prompt.approval_command,
)
return OpenPlan(
browse_plan=browse_plan,
@@ -90,6 +103,10 @@ def prepare_open(
setup_steps=setup_steps,
message="first-use setup approved; browser launch waits for transport readiness",
launch_spec=launch_spec,
setup_prompt_title=setup_prompt.title,
setup_prompt_body=setup_prompt.body,
setup_prompt_approve_label=setup_prompt.approve_label,
setup_prompt_approval_command=setup_prompt.approval_command,
)
return OpenPlan(
@@ -166,6 +183,56 @@ def _setup_steps(browse_plan: BrowsePlan) -> tuple[str, ...]:
return tuple(steps)
@dataclass(frozen=True)
class _SetupPrompt:
title: str
body: str
approve_label: str
approval_command: str
def _setup_prompt(browse_plan: BrowsePlan, setup_steps: tuple[str, ...]) -> _SetupPrompt:
transport = browse_plan.route.transport
label = _transport_label(transport)
platform = browse_plan.platform_capability.platform
endpoint = browse_plan.status.endpoint if browse_plan.status else "-"
setup_summary = "; ".join(setup_steps) if setup_steps else "prepare the transport"
if browse_plan.status and not browse_plan.status.installed:
availability = f"{label} is not installed or running."
else:
availability = f"{label} is not running."
if platform == "desktop":
management = f"AMPB can start a managed {label} session and keep its state isolated."
elif platform == "android":
management = f"AMPB can start a visible Android foreground {label} session."
elif platform == "ios":
management = f"AMPB can start a foreground-only in-app {label} session."
else:
management = f"AMPB can prepare {label} for this platform."
body = (
f"{availability} {management} The browser will use {endpoint} for "
f"{browse_plan.route.normalized}. Setup plan: {setup_summary}."
)
approval_command = shlex.join(("ampbrowser", "open", browse_plan.route.normalized, "--yes", "--launch"))
return _SetupPrompt(
title=f"Set up {label}?",
body=body,
approve_label=f"Start {label} and open",
approval_command=approval_command,
)
def _transport_label(transport: str) -> str:
labels = {
"tor": "Tor",
"i2p": "I2P",
"ipfs": "IPFS",
"gemini": "Gemini",
"reticulum": "Reticulum",
}
return labels.get(transport, transport)
def _launch_spec_for(
browse_plan: BrowsePlan,
*,
+25 -12
View File
@@ -158,17 +158,21 @@ def stop_managed_transport(
)
if not _pid_alive(pid):
_unlink_state(state_path)
cleaned = _unlink_state(state_path)
return ManagedTransportResult(
transport,
provider,
"stale",
"stale" if cleaned else "stale-cleanup-failed",
endpoint,
True,
pid,
state_dir,
command,
f"removed stale AMPB-owned {transport} state",
(
f"removed stale AMPB-owned {transport} state"
if cleaned
else f"could not remove stale AMPB-owned {transport} state"
),
)
try:
@@ -189,17 +193,21 @@ def stop_managed_transport(
deadline = time.monotonic() + wait_seconds
while time.monotonic() <= deadline:
if not _pid_alive(pid):
_unlink_state(state_path)
cleaned = _unlink_state(state_path)
return ManagedTransportResult(
transport,
provider,
"stopped",
"stopped" if cleaned else "stopped-cleanup-failed",
endpoint,
True,
pid,
state_dir,
command,
f"stopped AMPB-owned {transport}",
(
f"stopped AMPB-owned {transport}"
if cleaned
else f"stopped AMPB-owned {transport} but could not remove state"
),
)
time.sleep(0.1)
@@ -404,17 +412,21 @@ def _owned_transport_result(
command = tuple(str(part) for part in state.get("command", ()))
if not pid or not _pid_alive(pid):
_unlink_state(state_path)
cleaned = _unlink_state(state_path)
return ManagedTransportResult(
transport,
provider,
"stale",
"stale" if cleaned else "stale-cleanup-failed",
endpoint,
True,
pid,
state_dir,
command,
f"removed stale AMPB-owned {transport} state",
(
f"removed stale AMPB-owned {transport} state"
if cleaned
else f"could not remove stale AMPB-owned {transport} state"
),
)
if _wait_for_endpoint(endpoint, timeout_seconds=wait_seconds):
@@ -472,13 +484,14 @@ def _pid_alive(pid: int) -> bool:
return False
def _unlink_state(path: Path) -> None:
def _unlink_state(path: Path) -> bool:
try:
path.unlink()
return True
except FileNotFoundError:
pass
return True
except OSError:
pass
return False
def _endpoint_for(transport: str) -> str: