fsq-agent 0.1.0__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.
- fsq_agent/__init__.py +7 -0
- fsq_agent/_capability_bootstrap.py +72 -0
- fsq_agent/_workspace_paths.py +34 -0
- fsq_agent/adapters/SPEC.md +48 -0
- fsq_agent/adapters/__init__.py +4 -0
- fsq_agent/adapters/cli/__init__.py +6 -0
- fsq_agent/adapters/cli/_android_devices.py +63 -0
- fsq_agent/adapters/cli/_capability_bootstrap.py +18 -0
- fsq_agent/adapters/cli/_case_lifecycle.py +584 -0
- fsq_agent/adapters/cli/_composition.py +14 -0
- fsq_agent/adapters/cli/_core_execution.py +10 -0
- fsq_agent/adapters/cli/_env_file.py +73 -0
- fsq_agent/adapters/cli/_formatting.py +404 -0
- fsq_agent/adapters/cli/_llm_setup.py +114 -0
- fsq_agent/adapters/cli/_logging.py +22 -0
- fsq_agent/adapters/cli/_main.py +600 -0
- fsq_agent/adapters/cli/_strict_replay.py +82 -0
- fsq_agent/adapters/cli/_task_loader.py +48 -0
- fsq_agent/adapters/coding_agent/SPEC.md +51 -0
- fsq_agent/adapters/coding_agent/__init__.py +35 -0
- fsq_agent/adapters/coding_agent/_harness_tools.py +264 -0
- fsq_agent/adapters/coding_agent/_openai_runtime.py +1249 -0
- fsq_agent/adapters/control_plane/__init__.py +6 -0
- fsq_agent/adapters/control_plane/_cases.py +119 -0
- fsq_agent/adapters/control_plane/_config.py +131 -0
- fsq_agent/adapters/control_plane/_directory_picker.py +234 -0
- fsq_agent/adapters/control_plane/_evidence.py +522 -0
- fsq_agent/adapters/control_plane/_execution.py +499 -0
- fsq_agent/adapters/control_plane/_provider_auth.py +320 -0
- fsq_agent/adapters/control_plane/_readiness.py +108 -0
- fsq_agent/adapters/control_plane/_replay.py +182 -0
- fsq_agent/adapters/control_plane/_server.py +822 -0
- fsq_agent/adapters/control_plane/_state.py +320 -0
- fsq_agent/adapters/control_plane/_targets.py +152 -0
- fsq_agent/adapters/control_plane/_workspace_files.py +190 -0
- fsq_agent/adapters/control_plane/_workspaces.py +336 -0
- fsq_agent/adapters/control_plane/static/assets/controlPlane-CKe1qvza.js +36 -0
- fsq_agent/adapters/control_plane/static/assets/controlPlane-DUaxY_9c.css +1 -0
- fsq_agent/adapters/control_plane/static/control-plane/index.html +15 -0
- fsq_agent/adapters/control_plane/static/entry-assets.json +7 -0
- fsq_agent/agent/SPEC.md +160 -0
- fsq_agent/agent/__init__.py +10 -0
- fsq_agent/agent/_core.py +284 -0
- fsq_agent/agent/_events.py +24 -0
- fsq_agent/agent/_policy.py +60 -0
- fsq_agent/agent/_pre_plan.py +129 -0
- fsq_agent/agent/_prompt.py +125 -0
- fsq_agent/agent/_readiness.py +40 -0
- fsq_agent/agent/_runtime.py +33 -0
- fsq_agent/agent/_structured_output.py +57 -0
- fsq_agent/agent/_verification_task.py +199 -0
- fsq_agent/agent/_verifier.py +103 -0
- fsq_agent/agent/templates/agent_instructions.j2 +30 -0
- fsq_agent/agent/templates/task_input.j2 +18 -0
- fsq_agent/application/SPEC.md +118 -0
- fsq_agent/application/__init__.py +118 -0
- fsq_agent/application/_case.py +6 -0
- fsq_agent/application/_case_test.py +340 -0
- fsq_agent/application/_contracts.py +4 -0
- fsq_agent/application/_errors.py +44 -0
- fsq_agent/application/_provider.py +6 -0
- fsq_agent/application/_queries.py +8 -0
- fsq_agent/application/_workspace.py +6 -0
- fsq_agent/application/_workspace_init.py +203 -0
- fsq_agent/application/cases.py +110 -0
- fsq_agent/application/contracts/__init__.py +73 -0
- fsq_agent/application/contracts/cases.py +48 -0
- fsq_agent/application/contracts/doctor.py +65 -0
- fsq_agent/application/contracts/environments.py +11 -0
- fsq_agent/application/contracts/providers.py +25 -0
- fsq_agent/application/contracts/runs.py +133 -0
- fsq_agent/application/contracts/shared.py +66 -0
- fsq_agent/application/contracts/workspace.py +45 -0
- fsq_agent/application/doctor.py +191 -0
- fsq_agent/application/environments.py +25 -0
- fsq_agent/application/providers.py +76 -0
- fsq_agent/application/runs.py +307 -0
- fsq_agent/application/workspace.py +93 -0
- fsq_agent/capabilities/SPEC.md +97 -0
- fsq_agent/capabilities/__init__.py +14 -0
- fsq_agent/capabilities/_catalog.py +27 -0
- fsq_agent/capabilities/_decorators.py +301 -0
- fsq_agent/capabilities/_discovery.py +80 -0
- fsq_agent/case_dsl/SPEC.md +42 -0
- fsq_agent/case_dsl/__init__.py +7 -0
- fsq_agent/case_dsl/_loader.py +77 -0
- fsq_agent/case_dsl/_step_adapter.py +128 -0
- fsq_agent/cli/SPEC.md +153 -0
- fsq_agent/cli/__init__.py +6 -0
- fsq_agent/cli/__main__.py +7 -0
- fsq_agent/config/SPEC.md +160 -0
- fsq_agent/config/__init__.py +78 -0
- fsq_agent/config/_loader.py +445 -0
- fsq_agent/config/_paths.py +86 -0
- fsq_agent/config/_settings.py +51 -0
- fsq_agent/config/_user_provider.py +484 -0
- fsq_agent/config/_workspace.py +504 -0
- fsq_agent/control_plane/SPEC.md +186 -0
- fsq_agent/control_plane/__init__.py +6 -0
- fsq_agent/core/SPEC.md +233 -0
- fsq_agent/core/__init__.py +52 -0
- fsq_agent/core/_capabilities.py +57 -0
- fsq_agent/core/_default_capabilities.py +58 -0
- fsq_agent/core/_platform_runtime.py +8 -0
- fsq_agent/core/_platform_tools.py +8 -0
- fsq_agent/core/_runtime_secrets.py +48 -0
- fsq_agent/core/evidence/SPEC.md +45 -0
- fsq_agent/core/evidence/__init__.py +7 -0
- fsq_agent/core/evidence/_artifact_store.py +121 -0
- fsq_agent/core/evidence/_recorder.py +57 -0
- fsq_agent/core/harness/__init__.py +28 -0
- fsq_agent/core/harness/_ai_assertion_tool.py +8 -0
- fsq_agent/core/harness/_android.py +8 -0
- fsq_agent/core/harness/_android_devices.py +70 -0
- fsq_agent/core/harness/_android_driver.py +8 -0
- fsq_agent/core/harness/_appium_mac2_driver.py +8 -0
- fsq_agent/core/harness/_driver_tools.py +8 -0
- fsq_agent/core/harness/_factory.py +8 -0
- fsq_agent/core/harness/_interface.py +8 -0
- fsq_agent/core/harness/_macos.py +8 -0
- fsq_agent/core/harness/_macos_driver.py +8 -0
- fsq_agent/core/harness/_playwright_driver.py +8 -0
- fsq_agent/core/harness/_pywinauto_driver.py +8 -0
- fsq_agent/core/harness/_uiautomator2_driver.py +8 -0
- fsq_agent/core/harness/_web.py +8 -0
- fsq_agent/core/harness/_web_driver.py +8 -0
- fsq_agent/core/harness/_windows.py +8 -0
- fsq_agent/core/harness/_windows_driver.py +8 -0
- fsq_agent/core/interfaces/SPEC.md +48 -0
- fsq_agent/core/interfaces/__init__.py +26 -0
- fsq_agent/core/interfaces/_android_driver.py +57 -0
- fsq_agent/core/interfaces/_execution.py +30 -0
- fsq_agent/core/interfaces/_factories.py +57 -0
- fsq_agent/core/interfaces/_harness.py +57 -0
- fsq_agent/core/interfaces/_macos_driver.py +57 -0
- fsq_agent/core/interfaces/_web_driver.py +63 -0
- fsq_agent/core/interfaces/_windows_driver.py +54 -0
- fsq_agent/core/runner/SPEC.md +47 -0
- fsq_agent/core/runner/__init__.py +7 -0
- fsq_agent/core/runner/_runner.py +671 -0
- fsq_agent/core/runner/_sequence.py +43 -0
- fsq_agent/drivers/SPEC.md +46 -0
- fsq_agent/drivers/__init__.py +4 -0
- fsq_agent/drivers/_ai_assertion.py +137 -0
- fsq_agent/drivers/_capabilities.py +293 -0
- fsq_agent/drivers/_factory.py +55 -0
- fsq_agent/drivers/android/SPEC.md +29 -0
- fsq_agent/drivers/android/__init__.py +2 -0
- fsq_agent/drivers/android/_uiautomator2.py +517 -0
- fsq_agent/drivers/macos/SPEC.md +29 -0
- fsq_agent/drivers/macos/__init__.py +2 -0
- fsq_agent/drivers/macos/_appium_mac2.py +816 -0
- fsq_agent/drivers/web/SPEC.md +29 -0
- fsq_agent/drivers/web/__init__.py +2 -0
- fsq_agent/drivers/web/_playwright.py +612 -0
- fsq_agent/drivers/windows/SPEC.md +29 -0
- fsq_agent/drivers/windows/__init__.py +2 -0
- fsq_agent/drivers/windows/_pywinauto.py +503 -0
- fsq_agent/environments/SPEC.md +47 -0
- fsq_agent/environments/__init__.py +6 -0
- fsq_agent/environments/_service.py +151 -0
- fsq_agent/environments/providers/SPEC.md +31 -0
- fsq_agent/environments/providers/__init__.py +2 -0
- fsq_agent/environments/providers/_android.py +53 -0
- fsq_agent/environments/providers/_macos.py +6 -0
- fsq_agent/environments/providers/_runtime.py +37 -0
- fsq_agent/environments/providers/_web.py +46 -0
- fsq_agent/environments/providers/_windows.py +6 -0
- fsq_agent/execution/SPEC.md +75 -0
- fsq_agent/execution/__init__.py +48 -0
- fsq_agent/execution/deterministic.py +142 -0
- fsq_agent/execution/dynamic.py +70 -0
- fsq_agent/execution/lifecycle.py +672 -0
- fsq_agent/execution/recording.py +434 -0
- fsq_agent/execution/runs.py +185 -0
- fsq_agent/fsq/SPEC.md +179 -0
- fsq_agent/fsq/__init__.py +6 -0
- fsq_agent/fsq/_loader.py +8 -0
- fsq_agent/fsq/_step_adapter.py +8 -0
- fsq_agent/harnesses/SPEC.md +43 -0
- fsq_agent/harnesses/__init__.py +4 -0
- fsq_agent/harnesses/_android.py +273 -0
- fsq_agent/harnesses/_common_tools.py +126 -0
- fsq_agent/harnesses/_factory.py +116 -0
- fsq_agent/harnesses/_macos.py +305 -0
- fsq_agent/harnesses/_web.py +287 -0
- fsq_agent/harnesses/_windows.py +300 -0
- fsq_agent/knowledge/SPEC.md +40 -0
- fsq_agent/knowledge/__init__.py +7 -0
- fsq_agent/knowledge/_loader.py +74 -0
- fsq_agent/models/SPEC.md +301 -0
- fsq_agent/models/__init__.py +412 -0
- fsq_agent/models/_agent_io.py +68 -0
- fsq_agent/models/_ai_assertion.py +43 -0
- fsq_agent/models/_core.py +1117 -0
- fsq_agent/models/_devices.py +30 -0
- fsq_agent/models/_events.py +44 -0
- fsq_agent/models/_exceptions.py +28 -0
- fsq_agent/models/_fsq.py +117 -0
- fsq_agent/models/_knowledge.py +17 -0
- fsq_agent/models/_page_knowledge.py +116 -0
- fsq_agent/models/_platform_runtime.py +59 -0
- fsq_agent/models/_report.py +19 -0
- fsq_agent/models/_settings.py +621 -0
- fsq_agent/models/_skills.py +30 -0
- fsq_agent/models/_task.py +85 -0
- fsq_agent/models/_tools.py +175 -0
- fsq_agent/observation/SPEC.md +31 -0
- fsq_agent/observation/__init__.py +6 -0
- fsq_agent/observation/_logger.py +32 -0
- fsq_agent/providers/SPEC.md +103 -0
- fsq_agent/providers/__init__.py +50 -0
- fsq_agent/providers/_ai_assertion.py +134 -0
- fsq_agent/providers/_azure_openai.py +43 -0
- fsq_agent/providers/_case_suggestion.py +79 -0
- fsq_agent/providers/_connection_test.py +76 -0
- fsq_agent/providers/_factory.py +78 -0
- fsq_agent/providers/_github_copilot.py +413 -0
- fsq_agent/providers/_session.py +82 -0
- fsq_agent/report/SPEC.md +82 -0
- fsq_agent/report/__init__.py +18 -0
- fsq_agent/report/_core_evidence_report.py +286 -0
- fsq_agent/report/_evidence.py +30 -0
- fsq_agent/report/_failure_analysis.py +126 -0
- fsq_agent/report/_generator.py +293 -0
- fsq_agent/report/_resolver.py +36 -0
- fsq_agent/report/_static_html.py +133 -0
- fsq_agent/report/templates/.gitkeep +1 -0
- fsq_agent/resources/config.android.yaml +31 -0
- fsq_agent/resources/config.macos.yaml +33 -0
- fsq_agent/resources/config.web.yaml +32 -0
- fsq_agent/resources/config.windows.yaml +30 -0
- fsq_agent/resources/knowledge/skills/android-harness.md +80 -0
- fsq_agent/resources/knowledge/skills/automation-basics.md +6 -0
- fsq_agent/resources/knowledge/skills/macos-harness.md +127 -0
- fsq_agent/resources/knowledge/skills/web-harness.md +78 -0
- fsq_agent/resources/knowledge/skills/windows-harness.md +96 -0
- fsq_agent/skills/SPEC.md +48 -0
- fsq_agent/skills/__init__.py +7 -0
- fsq_agent/skills/_loader.py +74 -0
- fsq_agent/tools/SPEC.md +85 -0
- fsq_agent/tools/__init__.py +31 -0
- fsq_agent/tools/_agent_tools.py +260 -0
- fsq_agent/tools/_agents_adapter.py +439 -0
- fsq_agent/tools/_common.py +24 -0
- fsq_agent/tools/_file_ops.py +77 -0
- fsq_agent/tools/_tool_artifacts.py +101 -0
- fsq_agent-0.1.0.dist-info/METADATA +272 -0
- fsq_agent-0.1.0.dist-info/RECORD +252 -0
- fsq_agent-0.1.0.dist-info/WHEEL +4 -0
- fsq_agent-0.1.0.dist-info/entry_points.txt +3 -0
- fsq_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
fsq_agent/__init__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from fsq_agent.core import (
|
|
9
|
+
CapabilityDefinitionFactory,
|
|
10
|
+
CapabilityRegistry,
|
|
11
|
+
CommonPlatformTools,
|
|
12
|
+
)
|
|
13
|
+
from fsq_agent.tools import DefaultAgentToolProvider, FileOps
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from fsq_agent.models import CapabilityDefinition, HarnessPlatform
|
|
19
|
+
|
|
20
|
+
_CAPABILITY_DEFINITION_FACTORY = CapabilityDefinitionFactory()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def common_capability_definitions() -> list[CapabilityDefinition]:
|
|
24
|
+
return CommonPlatformTools.capability_definitions()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_capability_registry(*, platform: HarnessPlatform = "android", include_ai_assertion: bool = True) -> CapabilityRegistry:
|
|
28
|
+
return CapabilityRegistry.from_definitions(
|
|
29
|
+
[
|
|
30
|
+
*common_capability_definitions(),
|
|
31
|
+
*_platform_capability_definitions(platform, include_ai_assertion=include_ai_assertion),
|
|
32
|
+
]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def provider_required_capability_names(platform: HarnessPlatform) -> frozenset[str]:
|
|
37
|
+
full_snapshot = build_capability_registry(platform=platform, include_ai_assertion=True).snapshot()
|
|
38
|
+
provider_free_names = build_capability_registry(platform=platform, include_ai_assertion=False).snapshot().by_name()
|
|
39
|
+
return frozenset(capability.name for capability in full_snapshot.capabilities if capability.name not in provider_free_names)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def steps_require_provider(
|
|
43
|
+
steps: list[Any],
|
|
44
|
+
registry_snapshot: Any,
|
|
45
|
+
provider_required_names: frozenset[str],
|
|
46
|
+
) -> bool:
|
|
47
|
+
return any((capability := registry_snapshot.resolve(step.action_name)) is not None and capability.name in provider_required_names for step in steps)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _platform_capability_definitions(platform: HarnessPlatform, *, include_ai_assertion: bool) -> list[CapabilityDefinition]:
|
|
51
|
+
return _CAPABILITY_DEFINITION_FACTORY.platform_definitions(
|
|
52
|
+
platform=platform,
|
|
53
|
+
include_ai_assertion=include_ai_assertion,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_agent_tool_provider(
|
|
58
|
+
*,
|
|
59
|
+
read_roots: list[Path] | None = None,
|
|
60
|
+
write_root: Path | None = None,
|
|
61
|
+
runtime_secret_settings: Any = None,
|
|
62
|
+
local_tool_output_settings: Any = None,
|
|
63
|
+
runs_dir: Path | None = None,
|
|
64
|
+
run_id: str = "",
|
|
65
|
+
) -> DefaultAgentToolProvider:
|
|
66
|
+
return DefaultAgentToolProvider(
|
|
67
|
+
FileOps(read_roots=read_roots, write_root=write_root),
|
|
68
|
+
runtime_secret_settings=runtime_secret_settings,
|
|
69
|
+
local_tool_output_settings=local_tool_output_settings,
|
|
70
|
+
runs_dir=runs_dir,
|
|
71
|
+
run_id=run_id,
|
|
72
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from fsq_agent.models import ConfigurationError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_discovered_case_path(path: str | Path, discovery_root: Path) -> Path:
|
|
10
|
+
root = discovery_root.expanduser().resolve()
|
|
11
|
+
resolved = Path(path).expanduser().resolve()
|
|
12
|
+
try:
|
|
13
|
+
resolved.relative_to(root)
|
|
14
|
+
except ValueError as exc:
|
|
15
|
+
raise ConfigurationError(
|
|
16
|
+
"Discovered case path must stay within the case directory.",
|
|
17
|
+
context={"path": str(path)},
|
|
18
|
+
) from exc
|
|
19
|
+
return resolved
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_workspace_cases_path(path: str | Path, cases_dir: Path) -> Path:
|
|
23
|
+
root = cases_dir.expanduser().resolve()
|
|
24
|
+
requested = Path(path).expanduser()
|
|
25
|
+
candidate = requested if requested.is_absolute() else root / requested
|
|
26
|
+
resolved = candidate.resolve()
|
|
27
|
+
try:
|
|
28
|
+
resolved.relative_to(root)
|
|
29
|
+
except ValueError as exc:
|
|
30
|
+
raise ConfigurationError(
|
|
31
|
+
"Case path must stay within the workspace cases directory.",
|
|
32
|
+
context={"path": str(requested)},
|
|
33
|
+
) from exc
|
|
34
|
+
return resolved
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Module: adapters
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Own external protocol adaptation for the public CLI, local Control Plane, and coding-agent runtimes. Adapters decode external inputs, invoke inward public contracts, and project safe output. They do not own Application validation, execution semantics, persistence formats, capability policy, or platform automation.
|
|
6
|
+
|
|
7
|
+
## Dependencies
|
|
8
|
+
|
|
9
|
+
Adapters may import public APIs from `application`, `execution`, `models`, `config`, `providers`, `agent`, `case_dsl`, `core`, and `report`. Inward modules must not import `adapters`. Adapter subpackages must not form cycles.
|
|
10
|
+
|
|
11
|
+
## Public Interface
|
|
12
|
+
|
|
13
|
+
- `adapters.cli` owns the `fsq` Click command tree and exports `main`.
|
|
14
|
+
- `adapters.control_plane` owns `ControlPlaneServer`, `ControlPlaneServerOptions`, `run_control_plane`, and the documented HTTP/SSE/static API.
|
|
15
|
+
- `adapters.coding_agent` owns the OpenAI Agents SDK implementation of public Agent runtime protocols. Its concrete runtime and SDK tool adapters are private; composition roots consume its public runtime factory.
|
|
16
|
+
|
|
17
|
+
The installed scripts target canonical `fsq_agent.adapters.cli:main`. Existing `fsq_agent.cli` and `fsq_agent.control_plane` packages remain compatibility entries for documented public symbols only, and each compatibility symbol references the canonical adapter object. Old private transport submodule imports are unsupported and absent.
|
|
18
|
+
|
|
19
|
+
## Internal Structure
|
|
20
|
+
|
|
21
|
+
- `cli/`: CLI parsing, presentation, output modes, and exit mapping.
|
|
22
|
+
- `control_plane/`: Control Plane HTTP/SSE/static transport, projections, and state.
|
|
23
|
+
- `control_plane/static/`: Generated Control Plane package data.
|
|
24
|
+
- `coding_agent/`: OpenAI Agents SDK runtime construction, SDK tool/schema conversion, stream-event/result adaptation, and public runtime factory.
|
|
25
|
+
- Adapter-private `_*.py` files are not imported by inward modules.
|
|
26
|
+
|
|
27
|
+
## Python Architecture
|
|
28
|
+
|
|
29
|
+
- Architecture level: Level 3 Layered Application.
|
|
30
|
+
- Public API: transport entry points plus the Coding Agent runtime factory consumed by composition roots.
|
|
31
|
+
- Domain boundaries: external protocol decoding and presentation only.
|
|
32
|
+
- Boundary models: Click values and HTTP/JSON/SSE dictionaries remain at adapter boundaries.
|
|
33
|
+
- Dependency direction: adapters depend inward; Application and lower modules never import adapters.
|
|
34
|
+
- Rationale: the entry surfaces coordinate frameworks, state, static delivery, and inward services without requiring another domain layer.
|
|
35
|
+
|
|
36
|
+
## Error Handling
|
|
37
|
+
|
|
38
|
+
Adapters map stable inward failures into documented exit codes or safe HTTP/SSE responses. Compatibility paths preserve exception identity, state identity, redaction, and output behavior.
|
|
39
|
+
|
|
40
|
+
## Current Invariants
|
|
41
|
+
|
|
42
|
+
- Public compatibility packages do not duplicate transport implementations, package data, or mutable state.
|
|
43
|
+
- CLI and Control Plane business operations continue through Application.
|
|
44
|
+
- Control Plane HTML, JavaScript, CSS, `entry-assets.json`, and all referenced generated assets are runtime package data included in both wheel and source distribution. Installed `fsq ui` resolves these resources from the installed package without a frontend source tree, Node.js, or runtime asset download.
|
|
45
|
+
- CLI and Control Plane use the same public Execution services; adapter-private modules do not implement lifecycle or recording policy.
|
|
46
|
+
- CLI and Control Plane composition roots inject the same Coding Agent runtime implementation into SDK-neutral Agent orchestration. Application and inward packages never import `adapters.coding_agent`.
|
|
47
|
+
- CLI keeps this composition in a private helper rather than in command handlers. The helper may construct `FsqAgent` with the public Coding Agent runtime factory and return the SDK-neutral collaborator expected by Application, but it does not validate requests or own execution policy.
|
|
48
|
+
- Module relocation does not change CLI, HTTP, SSE, frontend, workspace, Case, evidence, report, Provider, or Runs contracts.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from fsq_agent.core import AndroidDeviceDiscovery
|
|
9
|
+
from fsq_agent.models import AndroidDevice, ConfigurationError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from fsq_agent.config import Settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def select_android_serial(settings: Settings, requested_serial: str | None) -> str | None:
|
|
16
|
+
serial = requested_serial.strip() if requested_serial is not None else None
|
|
17
|
+
if requested_serial is not None and not serial:
|
|
18
|
+
raise ConfigurationError("--android-serial requires a non-empty serial.")
|
|
19
|
+
if settings.harness.platform != "android":
|
|
20
|
+
if serial is not None:
|
|
21
|
+
raise ConfigurationError("--android-serial is only supported for Android workspaces.")
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
discovery = AndroidDeviceDiscovery().discover()
|
|
25
|
+
if discovery.error_code is not None:
|
|
26
|
+
raise ConfigurationError(
|
|
27
|
+
discovery.error_message or "ADB device discovery failed.",
|
|
28
|
+
context={"code": discovery.error_code},
|
|
29
|
+
)
|
|
30
|
+
devices = discovery.devices
|
|
31
|
+
if serial is not None:
|
|
32
|
+
matched = next((device for device in devices if device.serial == serial), None)
|
|
33
|
+
if matched is None:
|
|
34
|
+
raise ConfigurationError(
|
|
35
|
+
f"Android device {serial} was not discovered.",
|
|
36
|
+
context={"serial": serial, "devices": _device_states(devices)},
|
|
37
|
+
)
|
|
38
|
+
if matched.state != "device":
|
|
39
|
+
raise ConfigurationError(
|
|
40
|
+
f"Android device {serial} is not online (state: {matched.state}).",
|
|
41
|
+
context={"serial": serial, "state": matched.state},
|
|
42
|
+
)
|
|
43
|
+
settings.harness.android.serial = serial
|
|
44
|
+
return serial
|
|
45
|
+
|
|
46
|
+
online_devices = [device for device in devices if device.state == "device"]
|
|
47
|
+
if not online_devices:
|
|
48
|
+
raise ConfigurationError(
|
|
49
|
+
"No online Android devices were discovered.",
|
|
50
|
+
context={"devices": _device_states(devices)},
|
|
51
|
+
)
|
|
52
|
+
if len(online_devices) > 1:
|
|
53
|
+
raise ConfigurationError(
|
|
54
|
+
"Multiple online Android devices were discovered; select one with --android-serial.",
|
|
55
|
+
context={"serials": [device.serial for device in online_devices]},
|
|
56
|
+
)
|
|
57
|
+
selected_serial = online_devices[0].serial
|
|
58
|
+
settings.harness.android.serial = selected_serial
|
|
59
|
+
return selected_serial
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _device_states(devices: list[AndroidDevice]) -> dict[str, str]:
|
|
63
|
+
return {device.serial: device.state for device in devices}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from fsq_agent._capability_bootstrap import (
|
|
5
|
+
build_agent_tool_provider,
|
|
6
|
+
build_capability_registry,
|
|
7
|
+
common_capability_definitions,
|
|
8
|
+
provider_required_capability_names,
|
|
9
|
+
steps_require_provider,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"build_agent_tool_provider",
|
|
14
|
+
"build_capability_registry",
|
|
15
|
+
"common_capability_definitions",
|
|
16
|
+
"provider_required_capability_names",
|
|
17
|
+
"steps_require_provider",
|
|
18
|
+
]
|