tactus 0.33.0__py3-none-any.whl → 0.34.1__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.
- tactus/__init__.py +1 -1
- tactus/adapters/__init__.py +18 -1
- tactus/adapters/broker_log.py +127 -34
- tactus/adapters/channels/__init__.py +153 -0
- tactus/adapters/channels/base.py +174 -0
- tactus/adapters/channels/broker.py +179 -0
- tactus/adapters/channels/cli.py +448 -0
- tactus/adapters/channels/host.py +225 -0
- tactus/adapters/channels/ipc.py +297 -0
- tactus/adapters/channels/sse.py +305 -0
- tactus/adapters/cli_hitl.py +223 -1
- tactus/adapters/control_loop.py +879 -0
- tactus/adapters/file_storage.py +35 -2
- tactus/adapters/ide_log.py +7 -1
- tactus/backends/http_backend.py +0 -1
- tactus/broker/client.py +31 -1
- tactus/broker/server.py +416 -92
- tactus/cli/app.py +270 -7
- tactus/cli/control.py +393 -0
- tactus/core/config_manager.py +33 -6
- tactus/core/dsl_stubs.py +102 -18
- tactus/core/execution_context.py +265 -8
- tactus/core/lua_sandbox.py +8 -9
- tactus/core/registry.py +19 -2
- tactus/core/runtime.py +235 -27
- tactus/docker/Dockerfile.pypi +49 -0
- tactus/docs/__init__.py +33 -0
- tactus/docs/extractor.py +326 -0
- tactus/docs/html_renderer.py +72 -0
- tactus/docs/models.py +121 -0
- tactus/docs/templates/base.html +204 -0
- tactus/docs/templates/index.html +58 -0
- tactus/docs/templates/module.html +96 -0
- tactus/dspy/agent.py +382 -22
- tactus/dspy/broker_lm.py +57 -6
- tactus/dspy/config.py +14 -3
- tactus/dspy/history.py +2 -1
- tactus/dspy/module.py +136 -11
- tactus/dspy/signature.py +0 -1
- tactus/ide/server.py +300 -9
- tactus/primitives/human.py +619 -47
- tactus/primitives/system.py +0 -1
- tactus/protocols/__init__.py +25 -0
- tactus/protocols/control.py +427 -0
- tactus/protocols/notification.py +207 -0
- tactus/sandbox/container_runner.py +79 -11
- tactus/sandbox/docker_manager.py +23 -0
- tactus/sandbox/entrypoint.py +26 -0
- tactus/sandbox/protocol.py +3 -0
- tactus/stdlib/README.md +77 -0
- tactus/stdlib/__init__.py +27 -1
- tactus/stdlib/classify/__init__.py +165 -0
- tactus/stdlib/classify/classify.spec.tac +195 -0
- tactus/stdlib/classify/classify.tac +257 -0
- tactus/stdlib/classify/fuzzy.py +282 -0
- tactus/stdlib/classify/llm.py +319 -0
- tactus/stdlib/classify/primitive.py +287 -0
- tactus/stdlib/core/__init__.py +57 -0
- tactus/stdlib/core/base.py +320 -0
- tactus/stdlib/core/confidence.py +211 -0
- tactus/stdlib/core/models.py +161 -0
- tactus/stdlib/core/retry.py +171 -0
- tactus/stdlib/core/validation.py +274 -0
- tactus/stdlib/extract/__init__.py +125 -0
- tactus/stdlib/extract/llm.py +330 -0
- tactus/stdlib/extract/primitive.py +256 -0
- tactus/stdlib/tac/tactus/classify/base.tac +51 -0
- tactus/stdlib/tac/tactus/classify/fuzzy.tac +87 -0
- tactus/stdlib/tac/tactus/classify/index.md +77 -0
- tactus/stdlib/tac/tactus/classify/init.tac +29 -0
- tactus/stdlib/tac/tactus/classify/llm.tac +150 -0
- tactus/stdlib/tac/tactus/classify.spec.tac +191 -0
- tactus/stdlib/tac/tactus/extract/base.tac +138 -0
- tactus/stdlib/tac/tactus/extract/index.md +96 -0
- tactus/stdlib/tac/tactus/extract/init.tac +27 -0
- tactus/stdlib/tac/tactus/extract/llm.tac +201 -0
- tactus/stdlib/tac/tactus/extract.spec.tac +153 -0
- tactus/stdlib/tac/tactus/generate/base.tac +142 -0
- tactus/stdlib/tac/tactus/generate/index.md +195 -0
- tactus/stdlib/tac/tactus/generate/init.tac +28 -0
- tactus/stdlib/tac/tactus/generate/llm.tac +169 -0
- tactus/stdlib/tac/tactus/generate.spec.tac +210 -0
- tactus/testing/behave_integration.py +171 -7
- tactus/testing/context.py +0 -1
- tactus/testing/evaluation_runner.py +0 -1
- tactus/testing/gherkin_parser.py +0 -1
- tactus/testing/mock_hitl.py +0 -1
- tactus/testing/mock_tools.py +0 -1
- tactus/testing/models.py +0 -1
- tactus/testing/steps/builtin.py +0 -1
- tactus/testing/steps/custom.py +81 -22
- tactus/testing/steps/registry.py +0 -1
- tactus/testing/test_runner.py +7 -1
- tactus/validation/semantic_visitor.py +11 -5
- tactus/validation/validator.py +0 -1
- {tactus-0.33.0.dist-info → tactus-0.34.1.dist-info}/METADATA +14 -2
- {tactus-0.33.0.dist-info → tactus-0.34.1.dist-info}/RECORD +100 -49
- {tactus-0.33.0.dist-info → tactus-0.34.1.dist-info}/WHEEL +0 -0
- {tactus-0.33.0.dist-info → tactus-0.34.1.dist-info}/entry_points.txt +0 -0
- {tactus-0.33.0.dist-info → tactus-0.34.1.dist-info}/licenses/LICENSE +0 -0
tactus/cli/app.py
CHANGED
|
@@ -31,7 +31,6 @@ from tactus.validation import TactusValidator, ValidationMode
|
|
|
31
31
|
from tactus.formatting import TactusFormatter, FormattingError
|
|
32
32
|
from tactus.adapters.memory import MemoryStorage
|
|
33
33
|
from tactus.adapters.file_storage import FileStorage
|
|
34
|
-
from tactus.adapters.cli_hitl import CLIHITLHandler
|
|
35
34
|
|
|
36
35
|
# Setup rich console for pretty output
|
|
37
36
|
console = Console()
|
|
@@ -594,8 +593,8 @@ def run(
|
|
|
594
593
|
console.print(f"[red]Error:[/red] Unknown storage backend: {storage}")
|
|
595
594
|
raise typer.Exit(1)
|
|
596
595
|
|
|
597
|
-
#
|
|
598
|
-
hitl_handler =
|
|
596
|
+
# HITL handler will be set up later after procedure_id is known
|
|
597
|
+
hitl_handler = None
|
|
599
598
|
|
|
600
599
|
# Load configuration cascade
|
|
601
600
|
from tactus.core.config_manager import ConfigManager
|
|
@@ -627,6 +626,10 @@ def run(
|
|
|
627
626
|
if sandbox is not None:
|
|
628
627
|
# CLI flag overrides config
|
|
629
628
|
sandbox_config_dict["enabled"] = sandbox
|
|
629
|
+
else:
|
|
630
|
+
# CLI default: require sandbox unless explicitly disabled
|
|
631
|
+
if sandbox_config_dict.get("enabled") is None:
|
|
632
|
+
sandbox_config_dict["enabled"] = True
|
|
630
633
|
if sandbox_network is not None:
|
|
631
634
|
sandbox_config_dict["network"] = sandbox_network
|
|
632
635
|
if sandbox_broker_host is not None:
|
|
@@ -698,6 +701,23 @@ def run(
|
|
|
698
701
|
|
|
699
702
|
# Create runtime
|
|
700
703
|
procedure_id = f"cli-{workflow_file.stem}"
|
|
704
|
+
|
|
705
|
+
# Setup HITL handler - use new ControlLoopHandler with default channels
|
|
706
|
+
from tactus.adapters.channels import load_default_channels
|
|
707
|
+
from tactus.adapters.channels.cli import CLIControlChannel
|
|
708
|
+
from tactus.adapters.control_loop import ControlLoopHandler, ControlLoopHITLAdapter
|
|
709
|
+
|
|
710
|
+
# Load default channels (CLI if tty, IPC always)
|
|
711
|
+
# Then add CLI with custom console if not already present
|
|
712
|
+
channels = load_default_channels(procedure_id=procedure_id)
|
|
713
|
+
|
|
714
|
+
# If CLI channel not already loaded (because not tty), add it with custom console
|
|
715
|
+
if not any(c.channel_id == "cli" for c in channels):
|
|
716
|
+
channels.insert(0, CLIControlChannel(console=console))
|
|
717
|
+
|
|
718
|
+
control_handler = ControlLoopHandler(channels=channels, storage=storage_backend)
|
|
719
|
+
hitl_handler = ControlLoopHITLAdapter(control_handler)
|
|
720
|
+
|
|
701
721
|
runtime = TactusRuntime(
|
|
702
722
|
procedure_id=procedure_id,
|
|
703
723
|
storage_backend=storage_backend,
|
|
@@ -800,7 +820,48 @@ def run(
|
|
|
800
820
|
console.print(f"[dim]{sandbox_result.traceback}[/dim]")
|
|
801
821
|
else:
|
|
802
822
|
# Execute directly (non-sandboxed)
|
|
803
|
-
|
|
823
|
+
try:
|
|
824
|
+
result = asyncio.run(runtime.execute(source_content, context, format=file_format))
|
|
825
|
+
except Exception as e:
|
|
826
|
+
from tactus.core.exceptions import ProcedureWaitingForHuman
|
|
827
|
+
|
|
828
|
+
# Check both the exception itself and its __cause__
|
|
829
|
+
console.print(f"[dim]DEBUG: Caught exception type: {type(e).__name__}[/dim]")
|
|
830
|
+
console.print(
|
|
831
|
+
f"[dim]DEBUG: Exception __cause__ type: {type(e.__cause__).__name__ if e.__cause__ else 'None'}[/dim]"
|
|
832
|
+
)
|
|
833
|
+
console.print(
|
|
834
|
+
f"[dim]DEBUG: Is ProcedureWaitingForHuman: {isinstance(e, ProcedureWaitingForHuman)}[/dim]"
|
|
835
|
+
)
|
|
836
|
+
console.print(
|
|
837
|
+
f"[dim]DEBUG: __cause__ is ProcedureWaitingForHuman: {isinstance(e.__cause__, ProcedureWaitingForHuman) if e.__cause__ else False}[/dim]"
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
if isinstance(e, ProcedureWaitingForHuman):
|
|
841
|
+
# Direct exception
|
|
842
|
+
console.print(
|
|
843
|
+
"\n[yellow]⏸ Procedure paused - waiting for human response[/yellow]"
|
|
844
|
+
)
|
|
845
|
+
console.print(f"[dim]Message ID: {e.pending_message_id}[/dim]")
|
|
846
|
+
console.print("\n[cyan]The procedure has been paused and is waiting for input.")
|
|
847
|
+
console.print(
|
|
848
|
+
"To resume, run the procedure again or provide a response via another channel.[/cyan]\n"
|
|
849
|
+
)
|
|
850
|
+
return
|
|
851
|
+
elif e.__cause__ and isinstance(e.__cause__, ProcedureWaitingForHuman):
|
|
852
|
+
# Wrapped exception
|
|
853
|
+
console.print(
|
|
854
|
+
"\n[yellow]⏸ Procedure paused - waiting for human response[/yellow]"
|
|
855
|
+
)
|
|
856
|
+
console.print(f"[dim]Message ID: {e.__cause__.pending_message_id}[/dim]")
|
|
857
|
+
console.print("\n[cyan]The procedure has been paused and is waiting for input.")
|
|
858
|
+
console.print(
|
|
859
|
+
"To resume, run the procedure again or provide a response via another channel.[/cyan]\n"
|
|
860
|
+
)
|
|
861
|
+
return
|
|
862
|
+
else:
|
|
863
|
+
# Re-raise other exceptions
|
|
864
|
+
raise
|
|
804
865
|
|
|
805
866
|
if result["success"]:
|
|
806
867
|
console.print("\n[green]✓ Procedure completed successfully[/green]\n")
|
|
@@ -899,6 +960,7 @@ def sandbox_rebuild(
|
|
|
899
960
|
"""
|
|
900
961
|
from pathlib import Path
|
|
901
962
|
from tactus.sandbox import is_docker_available, DockerManager
|
|
963
|
+
from tactus.sandbox.docker_manager import resolve_dockerfile_path
|
|
902
964
|
import tactus
|
|
903
965
|
|
|
904
966
|
# Check Docker availability
|
|
@@ -909,7 +971,7 @@ def sandbox_rebuild(
|
|
|
909
971
|
|
|
910
972
|
# Get Tactus package path for build context
|
|
911
973
|
tactus_path = Path(tactus.__file__).parent.parent
|
|
912
|
-
dockerfile_path = tactus_path
|
|
974
|
+
dockerfile_path, build_mode = resolve_dockerfile_path(tactus_path)
|
|
913
975
|
|
|
914
976
|
if not dockerfile_path.exists():
|
|
915
977
|
console.print(f"[red]Error:[/red] Dockerfile not found: {dockerfile_path}")
|
|
@@ -933,6 +995,10 @@ def sandbox_rebuild(
|
|
|
933
995
|
console.print(f"[blue]Building sandbox image:[/blue] {manager.full_image_name}")
|
|
934
996
|
console.print(f"[dim]Version: {version}[/dim]")
|
|
935
997
|
console.print(f"[dim]Context: {tactus_path}[/dim]\n")
|
|
998
|
+
if build_mode == "pypi":
|
|
999
|
+
console.print(
|
|
1000
|
+
"[yellow]No source tree detected; building image by installing tactus from PyPI.[/yellow]"
|
|
1001
|
+
)
|
|
936
1002
|
|
|
937
1003
|
success, message = manager.build_image(
|
|
938
1004
|
dockerfile_path=dockerfile_path,
|
|
@@ -1423,7 +1489,10 @@ def test(
|
|
|
1423
1489
|
evaluator = TactusEvaluationRunner(
|
|
1424
1490
|
procedure_file, mock_tools=mock_tools, params=test_params
|
|
1425
1491
|
)
|
|
1426
|
-
evaluator.setup(
|
|
1492
|
+
evaluator.setup(
|
|
1493
|
+
result.registry.gherkin_specifications,
|
|
1494
|
+
custom_steps_dict=result.registry.custom_steps,
|
|
1495
|
+
)
|
|
1427
1496
|
|
|
1428
1497
|
if scenario:
|
|
1429
1498
|
eval_results = [evaluator.evaluate_scenario(scenario, runs, parallel)]
|
|
@@ -1436,7 +1505,10 @@ def test(
|
|
|
1436
1505
|
else:
|
|
1437
1506
|
# Run standard test
|
|
1438
1507
|
runner = TactusTestRunner(procedure_file, mock_tools=mock_tools, params=test_params)
|
|
1439
|
-
runner.setup(
|
|
1508
|
+
runner.setup(
|
|
1509
|
+
result.registry.gherkin_specifications,
|
|
1510
|
+
custom_steps_dict=result.registry.custom_steps,
|
|
1511
|
+
)
|
|
1440
1512
|
|
|
1441
1513
|
test_result = runner.run_tests(parallel=parallel, scenario_filter=scenario)
|
|
1442
1514
|
|
|
@@ -2211,6 +2283,195 @@ def trace_export(
|
|
|
2211
2283
|
raise typer.Exit(1)
|
|
2212
2284
|
|
|
2213
2285
|
|
|
2286
|
+
# =============================================================================
|
|
2287
|
+
# Stdlib Commands
|
|
2288
|
+
# =============================================================================
|
|
2289
|
+
|
|
2290
|
+
stdlib_app = typer.Typer(help="Manage Tactus standard library")
|
|
2291
|
+
app.add_typer(stdlib_app, name="stdlib")
|
|
2292
|
+
|
|
2293
|
+
|
|
2294
|
+
@stdlib_app.command("test")
|
|
2295
|
+
def stdlib_test(
|
|
2296
|
+
module: Optional[str] = typer.Argument(
|
|
2297
|
+
None, help="Specific module to test (e.g., 'classify', 'extract')"
|
|
2298
|
+
),
|
|
2299
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
2300
|
+
parallel: bool = typer.Option(True, "--parallel/--no-parallel", help="Run in parallel"),
|
|
2301
|
+
):
|
|
2302
|
+
"""
|
|
2303
|
+
Run BDD tests for standard library modules.
|
|
2304
|
+
|
|
2305
|
+
Examples:
|
|
2306
|
+
tactus stdlib test # Run all stdlib tests
|
|
2307
|
+
tactus stdlib test classify # Run only classify tests
|
|
2308
|
+
tactus stdlib test extract # Run only extract tests
|
|
2309
|
+
"""
|
|
2310
|
+
import tactus
|
|
2311
|
+
from tactus.validation import TactusValidator
|
|
2312
|
+
from tactus.testing.test_runner import TactusTestRunner
|
|
2313
|
+
|
|
2314
|
+
# Find stdlib spec files
|
|
2315
|
+
package_root = Path(tactus.__file__).parent
|
|
2316
|
+
stdlib_tac_path = package_root / "stdlib" / "tac" / "tactus"
|
|
2317
|
+
|
|
2318
|
+
# Find all .spec.tac files
|
|
2319
|
+
if module:
|
|
2320
|
+
# Test specific module
|
|
2321
|
+
spec_file = stdlib_tac_path / f"{module}.spec.tac"
|
|
2322
|
+
if not spec_file.exists():
|
|
2323
|
+
console.print(f"[red]Module spec not found: {spec_file}[/red]")
|
|
2324
|
+
raise typer.Exit(1)
|
|
2325
|
+
spec_files = [spec_file]
|
|
2326
|
+
else:
|
|
2327
|
+
# Test all modules
|
|
2328
|
+
spec_files = list(stdlib_tac_path.glob("*.spec.tac"))
|
|
2329
|
+
|
|
2330
|
+
if not spec_files:
|
|
2331
|
+
console.print("[yellow]No spec files found in stdlib[/yellow]")
|
|
2332
|
+
raise typer.Exit(0)
|
|
2333
|
+
|
|
2334
|
+
console.print(f"[cyan]Found {len(spec_files)} spec file(s) to test[/cyan]")
|
|
2335
|
+
|
|
2336
|
+
total_passed = 0
|
|
2337
|
+
total_failed = 0
|
|
2338
|
+
failed_modules = []
|
|
2339
|
+
|
|
2340
|
+
validator = TactusValidator()
|
|
2341
|
+
|
|
2342
|
+
for spec_file in sorted(spec_files):
|
|
2343
|
+
module_name = spec_file.stem.replace(".spec", "")
|
|
2344
|
+
console.print(f"\n[bold]Testing: {module_name}[/bold]")
|
|
2345
|
+
|
|
2346
|
+
# Validate and load specs
|
|
2347
|
+
result = validator.validate_file(str(spec_file))
|
|
2348
|
+
|
|
2349
|
+
if not result.valid:
|
|
2350
|
+
console.print(" [red]✗ Validation failed[/red]")
|
|
2351
|
+
for error in result.errors:
|
|
2352
|
+
console.print(f" {error.message}")
|
|
2353
|
+
total_failed += 1
|
|
2354
|
+
failed_modules.append(module_name)
|
|
2355
|
+
continue
|
|
2356
|
+
|
|
2357
|
+
if not result.registry or not result.registry.gherkin_specifications:
|
|
2358
|
+
console.print(" [yellow]⚠ No specifications found[/yellow]")
|
|
2359
|
+
continue
|
|
2360
|
+
|
|
2361
|
+
# Run tests
|
|
2362
|
+
try:
|
|
2363
|
+
runner = TactusTestRunner(spec_file, mock_tools={}, params={})
|
|
2364
|
+
runner.setup(
|
|
2365
|
+
result.registry.gherkin_specifications,
|
|
2366
|
+
custom_steps_dict=result.registry.custom_steps,
|
|
2367
|
+
)
|
|
2368
|
+
|
|
2369
|
+
test_result = runner.run_tests(parallel=parallel, scenario_filter=None)
|
|
2370
|
+
|
|
2371
|
+
# Display results
|
|
2372
|
+
passed = test_result.passed_scenarios
|
|
2373
|
+
failed = test_result.failed_scenarios
|
|
2374
|
+
total_passed += passed
|
|
2375
|
+
total_failed += failed
|
|
2376
|
+
|
|
2377
|
+
if failed > 0:
|
|
2378
|
+
console.print(f" [red]✗ {passed} passed, {failed} failed[/red]")
|
|
2379
|
+
for feature in test_result.features:
|
|
2380
|
+
for scenario in feature.scenarios:
|
|
2381
|
+
if scenario.status != "failed":
|
|
2382
|
+
continue
|
|
2383
|
+
console.print(f" [red]Scenario failed:[/red] {scenario.name}")
|
|
2384
|
+
for step in scenario.steps:
|
|
2385
|
+
if step.status != "failed":
|
|
2386
|
+
continue
|
|
2387
|
+
error_detail = step.error_message or "Unknown failure"
|
|
2388
|
+
console.print(
|
|
2389
|
+
f" [red]{step.keyword} {step.message}[/red]: {error_detail}"
|
|
2390
|
+
)
|
|
2391
|
+
failed_modules.append(module_name)
|
|
2392
|
+
else:
|
|
2393
|
+
console.print(f" [green]✓ {passed} scenarios passed[/green]")
|
|
2394
|
+
|
|
2395
|
+
runner.cleanup()
|
|
2396
|
+
|
|
2397
|
+
except Exception as e:
|
|
2398
|
+
console.print(f" [red]✗ Error: {e}[/red]")
|
|
2399
|
+
if verbose:
|
|
2400
|
+
console.print_exception()
|
|
2401
|
+
total_failed += 1
|
|
2402
|
+
failed_modules.append(module_name)
|
|
2403
|
+
|
|
2404
|
+
# Summary
|
|
2405
|
+
console.print("\n" + "=" * 50)
|
|
2406
|
+
console.print("[bold]Stdlib Test Summary[/bold]")
|
|
2407
|
+
console.print(f" Passed: [green]{total_passed}[/green]")
|
|
2408
|
+
console.print(f" Failed: [red]{total_failed}[/red]")
|
|
2409
|
+
|
|
2410
|
+
if failed_modules:
|
|
2411
|
+
console.print(f"\n[red]Failed modules: {', '.join(failed_modules)}[/red]")
|
|
2412
|
+
raise typer.Exit(1)
|
|
2413
|
+
|
|
2414
|
+
console.print("\n[green]All stdlib tests passed![/green]")
|
|
2415
|
+
|
|
2416
|
+
|
|
2417
|
+
# =============================================================================
|
|
2418
|
+
# Control Command
|
|
2419
|
+
# =============================================================================
|
|
2420
|
+
|
|
2421
|
+
|
|
2422
|
+
@app.command()
|
|
2423
|
+
def control(
|
|
2424
|
+
socket_path: Optional[str] = typer.Option(
|
|
2425
|
+
None,
|
|
2426
|
+
"--socket",
|
|
2427
|
+
"-s",
|
|
2428
|
+
help="Path to runtime's Unix socket (default: auto-detect from /tmp/tactus-control-*.sock)",
|
|
2429
|
+
),
|
|
2430
|
+
auto_respond: Optional[str] = typer.Option(
|
|
2431
|
+
None, "--respond", "-r", help="Auto-respond with this value (for testing)"
|
|
2432
|
+
),
|
|
2433
|
+
):
|
|
2434
|
+
"""
|
|
2435
|
+
Connect to running procedure and respond to control requests.
|
|
2436
|
+
|
|
2437
|
+
Opens an interactive session that connects to a running Tactus procedure
|
|
2438
|
+
via Unix socket IPC. Control requests from Human.approve() and similar
|
|
2439
|
+
calls will appear here, and you can respond to them.
|
|
2440
|
+
|
|
2441
|
+
This allows running the procedure in one terminal and responding to
|
|
2442
|
+
control requests from another terminal.
|
|
2443
|
+
"""
|
|
2444
|
+
from tactus.cli.control import main as control_main
|
|
2445
|
+
import glob
|
|
2446
|
+
|
|
2447
|
+
# Auto-detect socket path if not provided
|
|
2448
|
+
if socket_path is None:
|
|
2449
|
+
# Look for sockets in /tmp/tactus-control-*.sock
|
|
2450
|
+
socket_files = glob.glob("/tmp/tactus-control-*.sock")
|
|
2451
|
+
if not socket_files:
|
|
2452
|
+
console.print("[red]✗ No Tactus runtime sockets found[/red]")
|
|
2453
|
+
console.print("\n[yellow]Make sure a Tactus procedure is running:[/yellow]")
|
|
2454
|
+
console.print(" [dim]tactus run examples/90-hitl-simple.tac[/dim]")
|
|
2455
|
+
raise typer.Exit(1)
|
|
2456
|
+
elif len(socket_files) == 1:
|
|
2457
|
+
socket_path = socket_files[0]
|
|
2458
|
+
console.print(f"[dim]Auto-detected socket: {socket_path}[/dim]")
|
|
2459
|
+
else:
|
|
2460
|
+
console.print("[yellow]Multiple runtime sockets found:[/yellow]")
|
|
2461
|
+
for i, path in enumerate(socket_files, 1):
|
|
2462
|
+
console.print(f" [{i}] {path}")
|
|
2463
|
+
console.print()
|
|
2464
|
+
selection = Prompt.ask(
|
|
2465
|
+
"Select socket",
|
|
2466
|
+
choices=[str(i) for i in range(1, len(socket_files) + 1)],
|
|
2467
|
+
default="1",
|
|
2468
|
+
)
|
|
2469
|
+
socket_path = socket_files[int(selection) - 1]
|
|
2470
|
+
|
|
2471
|
+
# Run control CLI
|
|
2472
|
+
asyncio.run(control_main(socket_path, auto_respond))
|
|
2473
|
+
|
|
2474
|
+
|
|
2214
2475
|
def main():
|
|
2215
2476
|
"""Main entry point for the CLI."""
|
|
2216
2477
|
# Load configuration before processing any commands
|
|
@@ -2228,6 +2489,8 @@ def main():
|
|
|
2228
2489
|
"eval",
|
|
2229
2490
|
"version",
|
|
2230
2491
|
"ide",
|
|
2492
|
+
"stdlib",
|
|
2493
|
+
"control",
|
|
2231
2494
|
"trace-list",
|
|
2232
2495
|
"trace-show",
|
|
2233
2496
|
"trace-export",
|