loopx 0.4.8__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.
- loopx/__init__.py +5 -0
- loopx/agent_onboarding.py +654 -0
- loopx/agent_registry.py +112 -0
- loopx/ark_managed_agent_host.py +59 -0
- loopx/authority.py +805 -0
- loopx/benchmark.py +2875 -0
- loopx/benchmark_adapters/__init__.py +1 -0
- loopx/benchmark_adapters/agentissue.py +2644 -0
- loopx/benchmark_adapters/agents_last_exam.py +3998 -0
- loopx/benchmark_adapters/edgebench.py +322 -0
- loopx/benchmark_adapters/skillsbench.py +5978 -0
- loopx/benchmark_adapters/skillsbench_acp_failure_policy.py +143 -0
- loopx/benchmark_adapters/skillsbench_acp_process.py +31 -0
- loopx/benchmark_adapters/skillsbench_acp_relay.py +4832 -0
- loopx/benchmark_adapters/skillsbench_batch.py +124 -0
- loopx/benchmark_adapters/skillsbench_bridge_guard.py +209 -0
- loopx/benchmark_adapters/skillsbench_bridge_summary.py +203 -0
- loopx/benchmark_adapters/skillsbench_codex_goal_recovery.py +271 -0
- loopx/benchmark_adapters/skillsbench_codex_goal_trace.py +81 -0
- loopx/benchmark_adapters/skillsbench_codex_runtime.py +339 -0
- loopx/benchmark_adapters/skillsbench_dockerfile_runtime.py +467 -0
- loopx/benchmark_adapters/skillsbench_failure_signals.py +652 -0
- loopx/benchmark_adapters/skillsbench_proxy_runtime.py +327 -0
- loopx/benchmark_adapters/skillsbench_remote_bridge.py +402 -0
- loopx/benchmark_adapters/skillsbench_result_discovery.py +143 -0
- loopx/benchmark_adapters/skillsbench_runner_profile.py +436 -0
- loopx/benchmark_adapters/skillsbench_runner_source.py +99 -0
- loopx/benchmark_adapters/skillsbench_setup_preflight.py +771 -0
- loopx/benchmark_adapters/skillsbench_signals.py +15 -0
- loopx/benchmark_adapters/skillsbench_task_source.py +141 -0
- loopx/benchmark_adapters/skillsbench_turn_route.py +723 -0
- loopx/benchmark_adapters/skillsbench_turn_runtime.py +1069 -0
- loopx/benchmark_adapters/skillsbench_typed_repair.py +689 -0
- loopx/benchmark_adapters/skillsbench_uv_cache.py +111 -0
- loopx/benchmark_adapters/skillsbench_verifier_bootstrap.py +227 -0
- loopx/benchmark_adapters/skillsbench_verifier_cache.py +138 -0
- loopx/benchmark_adapters/terminal_bench.py +10078 -0
- loopx/benchmark_case_analysis.py +1276 -0
- loopx/benchmark_case_state.py +1079 -0
- loopx/benchmark_core/__init__.py +239 -0
- loopx/benchmark_core/adapter.py +84 -0
- loopx/benchmark_core/artifacts.py +517 -0
- loopx/benchmark_core/attempts.py +199 -0
- loopx/benchmark_core/container_exec.py +216 -0
- loopx/benchmark_core/io.py +68 -0
- loopx/benchmark_core/lifecycle.py +211 -0
- loopx/benchmark_core/loop_protocol.py +689 -0
- loopx/benchmark_core/observable_handles.py +348 -0
- loopx/benchmark_core/parity.py +256 -0
- loopx/benchmark_core/remote_closeout.py +482 -0
- loopx/benchmark_core/rounds.py +215 -0
- loopx/benchmark_core/route_profile.py +509 -0
- loopx/benchmark_core/run_permissions.py +206 -0
- loopx/benchmark_core/split_control.py +925 -0
- loopx/benchmark_core/turn_fidelity.py +326 -0
- loopx/benchmark_ledger.py +3793 -0
- loopx/benchmark_ledger_countability.py +372 -0
- loopx/benchmark_ledger_current.py +724 -0
- loopx/benchmark_trajectory.py +405 -0
- loopx/benchmarks/__init__.py +1 -0
- loopx/benchmarks/qualification/__init__.py +1 -0
- loopx/benchmarks/qualification/release_outcome_baseline.py +360 -0
- loopx/benchmarks/read_models/__init__.py +1 -0
- loopx/benchmarks/read_models/benchmark_attempt_accounting.py +53 -0
- loopx/benchmarks/read_models/benchmark_comparison.py +414 -0
- loopx/benchmarks/read_models/benchmark_event_timeline.py +113 -0
- loopx/benchmarks/read_models/benchmark_experiment_report.py +475 -0
- loopx/benchmarks/read_models/benchmark_learning_ledger.py +137 -0
- loopx/benchmarks/read_models/benchmark_lifecycle_contracts.py +228 -0
- loopx/benchmarks/read_models/benchmark_projection.py +723 -0
- loopx/benchmarks/read_models/benchmark_result.py +146 -0
- loopx/benchmarks/read_models/benchmark_run_execution_contract.py +116 -0
- loopx/benchmarks/read_models/benchmark_run_failure.py +157 -0
- loopx/benchmarks/read_models/benchmark_run_metrics.py +213 -0
- loopx/benchmarks/read_models/benchmark_run_post_execution.py +635 -0
- loopx/benchmarks/read_models/benchmark_run_pre_execution.py +541 -0
- loopx/benchmarks/read_models/benchmark_status_compaction.py +1255 -0
- loopx/benchmarks/read_models/benchmark_status_runner.py +780 -0
- loopx/benchmarks/read_models/goal_start_control_score.py +857 -0
- loopx/benchmarks/read_models/skillsbench_post_run_debug.py +746 -0
- loopx/benchmarks/read_models/skillsbench_verifier_attribution.py +269 -0
- loopx/bootstrap.py +1116 -0
- loopx/bootstrap_command_pack.py +2167 -0
- loopx/boundary_authority.py +199 -0
- loopx/canary/__init__.py +1 -0
- loopx/canary/maintainability_ratchet.py +800 -0
- loopx/canary/planner.py +1984 -0
- loopx/canary/premerge.py +1130 -0
- loopx/canary/qualification_profiles.py +309 -0
- loopx/canary/quality_surface_catalog.py +838 -0
- loopx/canary/release_profiles.py +51 -0
- loopx/canary/runner.py +1107 -0
- loopx/canary/smoke_health.py +581 -0
- loopx/canary/smoke_profiles.py +212 -0
- loopx/capabilities/__init__.py +0 -0
- loopx/capabilities/agent_turn_recall/__init__.py +17 -0
- loopx/capabilities/agent_turn_recall/cli.py +369 -0
- loopx/capabilities/agent_turn_recall/core.py +296 -0
- loopx/capabilities/auto_research/__init__.py +16 -0
- loopx/capabilities/auto_research/bootstrap_contract.py +157 -0
- loopx/capabilities/auto_research/cli.py +1468 -0
- loopx/capabilities/auto_research/core.py +11 -0
- loopx/capabilities/auto_research/defaults.py +79 -0
- loopx/capabilities/auto_research/demo_e2e.py +1848 -0
- loopx/capabilities/auto_research/demo_supervisor.py +186 -0
- loopx/capabilities/auto_research/evidence_packet.py +767 -0
- loopx/capabilities/auto_research/human_view.py +794 -0
- loopx/capabilities/auto_research/kernel.py +191 -0
- loopx/capabilities/auto_research/knn_demo_workspace.py +322 -0
- loopx/capabilities/auto_research/live_evidence.py +248 -0
- loopx/capabilities/auto_research/preset.py +176 -0
- loopx/capabilities/auto_research/research_state.py +1085 -0
- loopx/capabilities/auto_research/role_profiles.py +394 -0
- loopx/capabilities/auto_research/rollout_append.py +97 -0
- loopx/capabilities/auto_research/terminal_result_contract.py +422 -0
- loopx/capabilities/auto_research/terminal_result_projection.py +171 -0
- loopx/capabilities/auto_research/terminal_result_query.py +233 -0
- loopx/capabilities/auto_research/terminal_results.py +349 -0
- loopx/capabilities/auto_research/user_contract.py +190 -0
- loopx/capabilities/auto_research/worker_loop.py +163 -0
- loopx/capabilities/auto_research/worker_runtime.py +777 -0
- loopx/capabilities/auto_research/worker_skill/SKILL.md +343 -0
- loopx/capabilities/benchmark_toolkit/__init__.py +19 -0
- loopx/capabilities/benchmark_toolkit/integrity.py +387 -0
- loopx/capabilities/catalog.py +1875 -0
- loopx/capabilities/change_quality/__init__.py +19 -0
- loopx/capabilities/change_quality/cli.py +171 -0
- loopx/capabilities/change_quality/context.py +156 -0
- loopx/capabilities/change_quality/oracles.py +269 -0
- loopx/capabilities/change_quality/policy.py +34 -0
- loopx/capabilities/change_quality/receipt.py +482 -0
- loopx/capabilities/change_quality/result.py +493 -0
- loopx/capabilities/change_quality/scope.py +171 -0
- loopx/capabilities/change_quality/shadow.py +680 -0
- loopx/capabilities/content_ops/__init__.py +0 -0
- loopx/capabilities/content_ops/cli.py +649 -0
- loopx/capabilities/content_ops/connector_packets.py +164 -0
- loopx/capabilities/content_ops/item_lifecycle.py +1000 -0
- loopx/capabilities/content_ops/layout.py +451 -0
- loopx/capabilities/content_ops/markdown.py +456 -0
- loopx/capabilities/content_ops/schemas.py +51 -0
- loopx/capabilities/content_ops/social_browser_x.py +107 -0
- loopx/capabilities/content_ops/surface.py +1956 -0
- loopx/capabilities/content_ops/templates/layout-catalog-v0.json +72 -0
- loopx/capabilities/context_providers/__init__.py +36 -0
- loopx/capabilities/context_providers/base.py +189 -0
- loopx/capabilities/context_providers/factory.py +32 -0
- loopx/capabilities/context_providers/openviking.py +702 -0
- loopx/capabilities/context_providers/service_ownership.py +185 -0
- loopx/capabilities/decision_context/__init__.py +129 -0
- loopx/capabilities/decision_context/architecture.py +83 -0
- loopx/capabilities/decision_context/assembler.py +849 -0
- loopx/capabilities/decision_context/catalog_entry.py +195 -0
- loopx/capabilities/decision_context/cli.py +310 -0
- loopx/capabilities/decision_context/cursor_commit.py +535 -0
- loopx/capabilities/decision_context/outcome_feedback.py +352 -0
- loopx/capabilities/decision_context/packets.py +654 -0
- loopx/capabilities/decision_context/private_state.py +189 -0
- loopx/capabilities/decision_context/profile.py +453 -0
- loopx/capabilities/decision_context/providers.py +228 -0
- loopx/capabilities/decision_context/review_settlement.py +136 -0
- loopx/capabilities/decision_context/runtime.py +273 -0
- loopx/capabilities/decision_context/sources.py +415 -0
- loopx/capabilities/explore/__init__.py +1 -0
- loopx/capabilities/explore/activation.py +198 -0
- loopx/capabilities/explore/adaptive_replay_planner.py +221 -0
- loopx/capabilities/explore/child_replay_runtime.py +463 -0
- loopx/capabilities/explore/composition_frontier.py +291 -0
- loopx/capabilities/explore/counterfactual_runtime.py +578 -0
- loopx/capabilities/explore/episode_runtime.py +647 -0
- loopx/capabilities/explore/harness_checkpoint.py +171 -0
- loopx/capabilities/explore/harness_gate.py +115 -0
- loopx/capabilities/explore/harness_runtime.py +1124 -0
- loopx/capabilities/explore/replay_metrics.py +206 -0
- loopx/capabilities/explore/replay_runtime.py +1271 -0
- loopx/capabilities/explore/resource_portfolio.py +173 -0
- loopx/capabilities/explore/result_log.py +974 -0
- loopx/capabilities/explore/router_state.py +432 -0
- loopx/capabilities/explore/source_history_reconcile.py +255 -0
- loopx/capabilities/explore/speculative_scheduler.py +498 -0
- loopx/capabilities/explore/todo_branch_plan.py +650 -0
- loopx/capabilities/explore/todo_evidence.py +141 -0
- loopx/capabilities/explore/trace_runtime.py +284 -0
- loopx/capabilities/explore/worker_branch_plan.py +1257 -0
- loopx/capabilities/integration_branch/__init__.py +13 -0
- loopx/capabilities/integration_branch/cli.py +148 -0
- loopx/capabilities/integration_branch/core.py +916 -0
- loopx/capabilities/issue_fix/__init__.py +19 -0
- loopx/capabilities/issue_fix/acceptance_loop.py +1050 -0
- loopx/capabilities/issue_fix/candidate_evidence.py +503 -0
- loopx/capabilities/issue_fix/candidate_preflight.py +676 -0
- loopx/capabilities/issue_fix/cli.py +1822 -0
- loopx/capabilities/issue_fix/cli_input.py +87 -0
- loopx/capabilities/issue_fix/content_ops_cli.py +148 -0
- loopx/capabilities/issue_fix/discovered_issue_promotion.py +947 -0
- loopx/capabilities/issue_fix/explore_projection.py +710 -0
- loopx/capabilities/issue_fix/feasibility.py +542 -0
- loopx/capabilities/issue_fix/github_public.py +661 -0
- loopx/capabilities/issue_fix/intake_surface.py +832 -0
- loopx/capabilities/issue_fix/metadata_preview.py +218 -0
- loopx/capabilities/issue_fix/metrics_projection.py +1340 -0
- loopx/capabilities/issue_fix/metrics_supplement.py +634 -0
- loopx/capabilities/issue_fix/metrics_supplement_cli.py +127 -0
- loopx/capabilities/issue_fix/outcome_projection.py +1235 -0
- loopx/capabilities/issue_fix/periodic_report.py +189 -0
- loopx/capabilities/issue_fix/pr_description.py +418 -0
- loopx/capabilities/issue_fix/pr_gate_reconcile.py +496 -0
- loopx/capabilities/issue_fix/pr_gate_reconcile_cli.py +464 -0
- loopx/capabilities/issue_fix/pr_lifecycle.py +1327 -0
- loopx/capabilities/issue_fix/pr_lifecycle_rollout.py +85 -0
- loopx/capabilities/issue_fix/pr_monitor_materialization.py +257 -0
- loopx/capabilities/issue_fix/pr_review_ack.py +439 -0
- loopx/capabilities/issue_fix/provider_hooks.py +24 -0
- loopx/capabilities/issue_fix/repository_commit_evidence.py +186 -0
- loopx/capabilities/issue_fix/repository_context.py +457 -0
- loopx/capabilities/issue_fix/repository_memory.py +459 -0
- loopx/capabilities/issue_fix/repository_memory_provider.py +1454 -0
- loopx/capabilities/issue_fix/repository_snapshot.py +454 -0
- loopx/capabilities/issue_fix/reviewer_cli.py +917 -0
- loopx/capabilities/issue_fix/reviewer_notification.py +882 -0
- loopx/capabilities/issue_fix/reviewer_notification_drain.py +942 -0
- loopx/capabilities/issue_fix/reviewer_recommendation.py +1057 -0
- loopx/capabilities/issue_fix/reviewer_request.py +1282 -0
- loopx/capabilities/issue_fix/reward_memory.py +879 -0
- loopx/capabilities/issue_fix/workflow_plan.py +1286 -0
- loopx/capabilities/material_lifecycle/__init__.py +161 -0
- loopx/capabilities/material_lifecycle/_validation.py +183 -0
- loopx/capabilities/material_lifecycle/apply.py +672 -0
- loopx/capabilities/material_lifecycle/architecture.py +122 -0
- loopx/capabilities/material_lifecycle/cli.py +161 -0
- loopx/capabilities/material_lifecycle/decision_planning.py +470 -0
- loopx/capabilities/material_lifecycle/explore_execution.py +306 -0
- loopx/capabilities/material_lifecycle/intake.py +869 -0
- loopx/capabilities/material_lifecycle/inventory.py +147 -0
- loopx/capabilities/material_lifecycle/lifecycle.py +98 -0
- loopx/capabilities/material_lifecycle/preparation.py +147 -0
- loopx/capabilities/material_lifecycle/project_skill.py +83 -0
- loopx/capabilities/material_lifecycle/ranking.py +267 -0
- loopx/capabilities/material_lifecycle/readable_projection.py +500 -0
- loopx/capabilities/material_lifecycle/rebuild.py +480 -0
- loopx/capabilities/material_lifecycle/settlement.py +238 -0
- loopx/capabilities/periodic_report/__init__.py +71 -0
- loopx/capabilities/periodic_report/adapters.py +939 -0
- loopx/capabilities/periodic_report/archive.py +422 -0
- loopx/capabilities/periodic_report/bindings.py +705 -0
- loopx/capabilities/periodic_report/cli.py +277 -0
- loopx/capabilities/periodic_report/core.py +691 -0
- loopx/capabilities/periodic_report/extension_envelope.py +66 -0
- loopx/capabilities/periodic_report/presets.py +103 -0
- loopx/capabilities/periodic_report/profile.py +235 -0
- loopx/capabilities/periodic_report/project_progress.py +179 -0
- loopx/capabilities/periodic_report/triggers.py +452 -0
- loopx/capabilities/pr_review_queue/__init__.py +17 -0
- loopx/capabilities/pr_review_queue/core.py +506 -0
- loopx/capabilities/pr_review_queue/review_contract.py +506 -0
- loopx/capabilities/registry.py +192 -0
- loopx/capabilities/reward_memory/__init__.py +75 -0
- loopx/capabilities/reward_memory/application.py +819 -0
- loopx/capabilities/reward_memory/architecture.py +572 -0
- loopx/capabilities/reward_memory/candidate_review.py +511 -0
- loopx/capabilities/reward_memory/cli.py +469 -0
- loopx/capabilities/reward_memory/dogfood.py +574 -0
- loopx/capabilities/reward_memory/evaluation.py +296 -0
- loopx/capabilities/reward_memory/evaluation_fixtures.py +362 -0
- loopx/capabilities/reward_memory/experiment.py +567 -0
- loopx/capabilities/reward_memory/health.py +222 -0
- loopx/capabilities/reward_memory/ingestion.py +519 -0
- loopx/capabilities/reward_memory/registry.py +600 -0
- loopx/capabilities/reward_memory/runtime_hooks.py +312 -0
- loopx/capabilities/reward_memory/scoped_feedback.py +173 -0
- loopx/capabilities/semantic_preference/__init__.py +12 -0
- loopx/capabilities/semantic_preference/cli.py +189 -0
- loopx/capabilities/semantic_preference/contract.py +592 -0
- loopx/capabilities/semantic_preference/reward_memory.py +62 -0
- loopx/capabilities/value_connectors/__init__.py +1 -0
- loopx/capabilities/value_connectors/cli.py +401 -0
- loopx/capabilities/value_connectors/finance_extension_migration.py +108 -0
- loopx/capabilities/value_connectors/install_check.py +147 -0
- loopx/capabilities/value_connectors/planner.py +733 -0
- loopx/capabilities/value_connectors/source_map.py +446 -0
- loopx/claude_goal_baseline.py +138 -0
- loopx/claude_goal_mode/__init__.py +23 -0
- loopx/claude_goal_mode/hooks/goal_policy.py +212 -0
- loopx/claude_goal_mode/hooks/goal_state.py +139 -0
- loopx/claude_goal_mode/mcp/loopx_mcp.py +167 -0
- loopx/claude_goal_mode/scripts/connect.py +103 -0
- loopx/claude_goal_mode/scripts/goalmode_cmd.py +241 -0
- loopx/claude_goal_mode/scripts/install.py +328 -0
- loopx/claude_goal_mode/statusline/goal_status.py +97 -0
- loopx/cli.py +836 -0
- loopx/cli_commands/__init__.py +334 -0
- loopx/cli_commands/_host_thread.py +13 -0
- loopx/cli_commands/agentissue_runner_flow.py +447 -0
- loopx/cli_commands/agents_last_exam.py +160 -0
- loopx/cli_commands/agents_last_exam_baked_input.py +302 -0
- loopx/cli_commands/agents_last_exam_host_codex.py +374 -0
- loopx/cli_commands/agents_last_exam_launch_dry_run.py +372 -0
- loopx/cli_commands/agents_last_exam_local_plan.py +322 -0
- loopx/cli_commands/agents_last_exam_runner_source.py +352 -0
- loopx/cli_commands/agents_last_exam_task_material.py +335 -0
- loopx/cli_commands/agents_last_exam_validation_gate.py +236 -0
- loopx/cli_commands/benchmark_boundary.py +499 -0
- loopx/cli_commands/benchmark_dispatch.py +161 -0
- loopx/cli_commands/benchmark_release_outcome.py +123 -0
- loopx/cli_commands/benchmark_review_lifecycle.py +1275 -0
- loopx/cli_commands/benchmark_run_ledger.py +763 -0
- loopx/cli_commands/benchmark_run_ledger_case_analysis.py +249 -0
- loopx/cli_commands/benchmark_run_ledger_classification.py +45 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance.py +486 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance_registration.py +342 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance_rendering.py +233 -0
- loopx/cli_commands/benchmark_run_ledger_parity.py +92 -0
- loopx/cli_commands/bootstrap_connect.py +238 -0
- loopx/cli_commands/canary.py +707 -0
- loopx/cli_commands/canary_release_qualification.py +79 -0
- loopx/cli_commands/capability.py +96 -0
- loopx/cli_commands/doctor.py +43 -0
- loopx/cli_commands/dreaming.py +143 -0
- loopx/cli_commands/edgebench.py +205 -0
- loopx/cli_commands/evidence_log.py +275 -0
- loopx/cli_commands/explore.py +989 -0
- loopx/cli_commands/explore_planning_commands.py +157 -0
- loopx/cli_commands/extension.py +271 -0
- loopx/cli_commands/first_run_report.py +73 -0
- loopx/cli_commands/goal_channel.py +656 -0
- loopx/cli_commands/handoff_mode.py +158 -0
- loopx/cli_commands/history.py +622 -0
- loopx/cli_commands/host_mode_plan.py +113 -0
- loopx/cli_commands/lark_inbox.py +431 -0
- loopx/cli_commands/lark_kanban.py +629 -0
- loopx/cli_commands/ml_experiment.py +321 -0
- loopx/cli_commands/multi_agent.py +211 -0
- loopx/cli_commands/opencode2_goal_worker.py +217 -0
- loopx/cli_commands/pr_review.py +167 -0
- loopx/cli_commands/presentation.py +218 -0
- loopx/cli_commands/preset.py +96 -0
- loopx/cli_commands/project.py +150 -0
- loopx/cli_commands/project_lifecycle.py +915 -0
- loopx/cli_commands/quota.py +859 -0
- loopx/cli_commands/quota_registration.py +241 -0
- loopx/cli_commands/quota_request.py +113 -0
- loopx/cli_commands/ready_score.py +110 -0
- loopx/cli_commands/registry_admin.py +975 -0
- loopx/cli_commands/registry_admin_configure.py +344 -0
- loopx/cli_commands/registry_admin_peer.py +84 -0
- loopx/cli_commands/registry_authority.py +218 -0
- loopx/cli_commands/review_batch.py +146 -0
- loopx/cli_commands/slash_commands.py +145 -0
- loopx/cli_commands/start_goal.py +251 -0
- loopx/cli_commands/starter.py +175 -0
- loopx/cli_commands/starter_bootstrap.py +179 -0
- loopx/cli_commands/starter_bootstrap_registration.py +198 -0
- loopx/cli_commands/starter_runtime_idle.py +107 -0
- loopx/cli_commands/starter_scheduler.py +207 -0
- loopx/cli_commands/starter_session_runtime.py +152 -0
- loopx/cli_commands/starter_visible_common.py +54 -0
- loopx/cli_commands/starter_visible_driver.py +161 -0
- loopx/cli_commands/starter_visible_pilot.py +278 -0
- loopx/cli_commands/status.py +867 -0
- loopx/cli_commands/status_registration.py +239 -0
- loopx/cli_commands/summary_all.py +222 -0
- loopx/cli_commands/support_control.py +809 -0
- loopx/cli_commands/support_control_registry.py +68 -0
- loopx/cli_commands/support_control_supervisor.py +289 -0
- loopx/cli_commands/task_lease.py +306 -0
- loopx/cli_commands/terminal_bench_adapter.py +717 -0
- loopx/cli_commands/terminal_bench_environment_result.py +1246 -0
- loopx/cli_commands/todo.py +940 -0
- loopx/cli_commands/todo_argument_validation.py +572 -0
- loopx/cli_commands/todo_event.py +114 -0
- loopx/cli_commands/turn.py +804 -0
- loopx/cli_commands/version.py +46 -0
- loopx/cli_commands/worker_bridge.py +659 -0
- loopx/cli_rollout.py +314 -0
- loopx/codex_cli_goal_tui.py +672 -0
- loopx/codex_cli_probe.py +1530 -0
- loopx/codex_cli_probe_markdown.py +935 -0
- loopx/codex_cli_runtime_probe.py +733 -0
- loopx/codex_cli_scheduler.py +564 -0
- loopx/codex_goal_baseline.py +620 -0
- loopx/configuration_catalog.py +617 -0
- loopx/configure_goal.py +1375 -0
- loopx/contract.py +996 -0
- loopx/control_plane/__init__.py +71 -0
- loopx/control_plane/agents/__init__.py +1 -0
- loopx/control_plane/agents/agent_lane_recommendation.py +516 -0
- loopx/control_plane/agents/agent_scope.py +1578 -0
- loopx/control_plane/agents/agent_scope_frontier.py +60 -0
- loopx/control_plane/agents/capability_gate.py +531 -0
- loopx/control_plane/agents/identity.py +140 -0
- loopx/control_plane/agents/legacy_migration.py +169 -0
- loopx/control_plane/agents/management_projection.py +658 -0
- loopx/control_plane/agents/material_frontier.py +608 -0
- loopx/control_plane/agents/material_handoff.py +156 -0
- loopx/control_plane/agents/multi_agent/__init__.py +1 -0
- loopx/control_plane/agents/multi_agent/codex_executable.py +207 -0
- loopx/control_plane/agents/multi_agent/collective_round_ledger.py +387 -0
- loopx/control_plane/agents/multi_agent/contract.py +474 -0
- loopx/control_plane/agents/multi_agent/recipe.py +110 -0
- loopx/control_plane/agents/multi_agent/role_successor.py +297 -0
- loopx/control_plane/agents/multi_agent/runtime_scripts.py +426 -0
- loopx/control_plane/agents/multi_agent/visible_launch_policy.py +149 -0
- loopx/control_plane/agents/multi_agent/visible_wake_scheduler.py +392 -0
- loopx/control_plane/agents/profile.py +216 -0
- loopx/control_plane/agents/runtime_model.py +73 -0
- loopx/control_plane/agents/subagent_activity.py +164 -0
- loopx/control_plane/agents/supervisor.py +544 -0
- loopx/control_plane/agents/supervisor_events.py +462 -0
- loopx/control_plane/agents/supervisor_inject.py +204 -0
- loopx/control_plane/agents/work_mode.py +56 -0
- loopx/control_plane/agents/workspace_guard.py +364 -0
- loopx/control_plane/effect_program.py +644 -0
- loopx/control_plane/goals/__init__.py +1 -0
- loopx/control_plane/goals/active_state_event_projection.py +103 -0
- loopx/control_plane/goals/active_state_metadata.py +47 -0
- loopx/control_plane/goals/active_state_sections.py +58 -0
- loopx/control_plane/goals/configure_goal_service.py +354 -0
- loopx/control_plane/goals/contract_health.py +132 -0
- loopx/control_plane/goals/dreaming.py +152 -0
- loopx/control_plane/goals/global_registry_health.py +199 -0
- loopx/control_plane/goals/global_registry_shadow.py +33 -0
- loopx/control_plane/goals/goal_channel.py +34 -0
- loopx/control_plane/goals/goal_channel_projection.py +560 -0
- loopx/control_plane/goals/goal_frontier/__init__.py +1917 -0
- loopx/control_plane/goals/goal_frontier/ack_policy.py +149 -0
- loopx/control_plane/goals/goal_frontier/outcome_continuity.py +437 -0
- loopx/control_plane/goals/goal_frontier/replan_rules.py +210 -0
- loopx/control_plane/goals/goal_frontier/semantic_history.py +314 -0
- loopx/control_plane/goals/goal_frontier/terminal.py +180 -0
- loopx/control_plane/goals/goal_vision.py +443 -0
- loopx/control_plane/goals/goal_vision_policy.py +36 -0
- loopx/control_plane/goals/goal_vision_state.py +62 -0
- loopx/control_plane/goals/goal_vision_wait.py +290 -0
- loopx/control_plane/goals/path_resolution.py +20 -0
- loopx/control_plane/goals/start_contract.py +206 -0
- loopx/control_plane/goals/vision_checkpoint.py +92 -0
- loopx/control_plane/handoff/__init__.py +1 -0
- loopx/control_plane/handoff/cross_runtime_impl_review.py +311 -0
- loopx/control_plane/handoff/delivery_contract.py +161 -0
- loopx/control_plane/handoff/handoff_runs.py +71 -0
- loopx/control_plane/handoff/project_handoff.py +155 -0
- loopx/control_plane/handoff/review_batch.py +463 -0
- loopx/control_plane/handoff/review_packet_context.py +216 -0
- loopx/control_plane/heartbeat/agent.py +173 -0
- loopx/control_plane/heartbeat/budget.py +66 -0
- loopx/control_plane/heartbeat/builder.py +501 -0
- loopx/control_plane/heartbeat/host.py +64 -0
- loopx/control_plane/heartbeat/rules.py +68 -0
- loopx/control_plane/heartbeat/task_body.py +759 -0
- loopx/control_plane/heartbeat/visible_goal.py +86 -0
- loopx/control_plane/projects/__init__.py +1 -0
- loopx/control_plane/projects/contract.py +25 -0
- loopx/control_plane/projects/registry.py +663 -0
- loopx/control_plane/quota/__init__.py +1 -0
- loopx/control_plane/quota/cli_projection.py +704 -0
- loopx/control_plane/quota/decision_summary.py +431 -0
- loopx/control_plane/quota/effect_program.py +152 -0
- loopx/control_plane/quota/error_codes.py +19 -0
- loopx/control_plane/quota/goal_boundary.py +464 -0
- loopx/control_plane/quota/heartbeat_receipt.py +277 -0
- loopx/control_plane/quota/heartbeat_recommendation.py +718 -0
- loopx/control_plane/quota/host_poll_receipts.py +162 -0
- loopx/control_plane/quota/live_decision.py +142 -0
- loopx/control_plane/quota/monitor_poll.py +786 -0
- loopx/control_plane/quota/policy_constants.py +40 -0
- loopx/control_plane/quota/projection_repair.py +262 -0
- loopx/control_plane/quota/recent_runs.py +210 -0
- loopx/control_plane/quota/scheduler_ack.py +490 -0
- loopx/control_plane/quota/selected_todo_projection.py +139 -0
- loopx/control_plane/quota/settlement.py +437 -0
- loopx/control_plane/quota/settlement_cli.py +246 -0
- loopx/control_plane/quota/settlement_validation.py +64 -0
- loopx/control_plane/quota/settlement_workspace_causality.py +180 -0
- loopx/control_plane/quota/should_run.py +249 -0
- loopx/control_plane/quota/should_run_packet.py +1165 -0
- loopx/control_plane/quota/should_run_prepare.py +675 -0
- loopx/control_plane/quota/slot_accounting.py +1123 -0
- loopx/control_plane/quota/spend_sources.py +11 -0
- loopx/control_plane/quota/stall_repair.py +397 -0
- loopx/control_plane/quota/states.py +29 -0
- loopx/control_plane/quota/task_orchestration.py +448 -0
- loopx/control_plane/quota/task_orchestration_admission.py +497 -0
- loopx/control_plane/quota/turn_envelope.py +889 -0
- loopx/control_plane/quota/usage_summary.py +140 -0
- loopx/control_plane/reward_memory.py +43 -0
- loopx/control_plane/runtime/__init__.py +2 -0
- loopx/control_plane/runtime/active_user_assisted_pilot.py +275 -0
- loopx/control_plane/runtime/agent_scoped_evidence_log.py +435 -0
- loopx/control_plane/runtime/decision_freshness.py +203 -0
- loopx/control_plane/runtime/event_ledger.py +197 -0
- loopx/control_plane/runtime/event_store_migration_bridge.py +196 -0
- loopx/control_plane/runtime/goal_project_route.py +70 -0
- loopx/control_plane/runtime/local_state_write_correctness.py +242 -0
- loopx/control_plane/runtime/promotion_readiness.py +152 -0
- loopx/control_plane/runtime/public_safety.py +120 -0
- loopx/control_plane/runtime/run_artifacts.py +78 -0
- loopx/control_plane/runtime/run_compaction.py +397 -0
- loopx/control_plane/runtime/run_context_retention.py +241 -0
- loopx/control_plane/runtime/run_history.py +132 -0
- loopx/control_plane/runtime/run_index_duplicates.py +205 -0
- loopx/control_plane/runtime/run_index_rebuild.py +263 -0
- loopx/control_plane/runtime/run_ingest_health.py +336 -0
- loopx/control_plane/runtime/runtime_projection_route.py +624 -0
- loopx/control_plane/runtime/runtime_projection_writer.py +98 -0
- loopx/control_plane/runtime/session_runtime.py +339 -0
- loopx/control_plane/runtime/shared_runtime_material_projection.py +332 -0
- loopx/control_plane/runtime/shared_runtime_refresh_projection.py +183 -0
- loopx/control_plane/runtime/stale_latest_run.py +90 -0
- loopx/control_plane/runtime/status_classifications.py +49 -0
- loopx/control_plane/runtime/status_projection_cache.py +235 -0
- loopx/control_plane/runtime/stride_observation.py +144 -0
- loopx/control_plane/runtime/time.py +39 -0
- loopx/control_plane/runtime/trajectory_hygiene.py +149 -0
- loopx/control_plane/runtime/validation_command.py +69 -0
- loopx/control_plane/scheduler/__init__.py +1 -0
- loopx/control_plane/scheduler/ack.py +329 -0
- loopx/control_plane/scheduler/arbitration.py +188 -0
- loopx/control_plane/scheduler/automation_liveness.py +183 -0
- loopx/control_plane/scheduler/execution_context.py +555 -0
- loopx/control_plane/scheduler/external_evidence_observation.py +428 -0
- loopx/control_plane/scheduler/monitor_display.py +143 -0
- loopx/control_plane/scheduler/monitor_poll_policy.py +161 -0
- loopx/control_plane/scheduler/monitor_poll_writeback.py +351 -0
- loopx/control_plane/scheduler/monitor_target.py +64 -0
- loopx/control_plane/scheduler/monitor_todo.py +146 -0
- loopx/control_plane/scheduler/monitor_wait.py +237 -0
- loopx/control_plane/scheduler/scheduler_hint.py +1284 -0
- loopx/control_plane/scheduler/state.py +354 -0
- loopx/control_plane/scheduler/state_transition_rules.py +179 -0
- loopx/control_plane/scheduler/time.py +10 -0
- loopx/control_plane/settlement_driver.py +293 -0
- loopx/control_plane/status/__init__.py +6 -0
- loopx/control_plane/status/active_state_projection.py +105 -0
- loopx/control_plane/status/agent_lane_projection.py +375 -0
- loopx/control_plane/status/attention_projection.py +74 -0
- loopx/control_plane/status/autonomous_replan_projection.py +103 -0
- loopx/control_plane/status/collection.py +140 -0
- loopx/control_plane/status/contract_projection.py +31 -0
- loopx/control_plane/status/dreaming_projection.py +52 -0
- loopx/control_plane/status/goal_attention_projection.py +157 -0
- loopx/control_plane/status/lifecycle_projection.py +110 -0
- loopx/control_plane/status/monitor_display_projection.py +69 -0
- loopx/control_plane/status/registry_health_projection.py +75 -0
- loopx/control_plane/status/run_projection.py +70 -0
- loopx/control_plane/status/runtime_summaries.py +161 -0
- loopx/control_plane/testing/__init__.py +1 -0
- loopx/control_plane/testing/actual_default_model_behavior_portfolio.py +1371 -0
- loopx/control_plane/testing/canary_harness.py +182 -0
- loopx/control_plane/testing/capability_monitor_repair_tool_behavior.py +674 -0
- loopx/control_plane/testing/cli_output_budget.py +807 -0
- loopx/control_plane/testing/cli_output_differential.py +250 -0
- loopx/control_plane/testing/cli_output_semantics.py +87 -0
- loopx/control_plane/testing/control_plane_composition_scenarios.py +225 -0
- loopx/control_plane/testing/decision_replay.py +268 -0
- loopx/control_plane/testing/doubao_model_behavior_actor.py +559 -0
- loopx/control_plane/testing/model_behavior_corpus.py +344 -0
- loopx/control_plane/testing/model_behavior_qualification.py +769 -0
- loopx/control_plane/testing/model_behavior_retained_cases.py +235 -0
- loopx/control_plane/testing/model_tool_behavior.py +536 -0
- loopx/control_plane/testing/onboarding_model_behavior_qualification.py +642 -0
- loopx/control_plane/testing/quota_fixtures.py +208 -0
- loopx/control_plane/testing/quota_should_run_parity.py +57 -0
- loopx/control_plane/testing/release_commit_qualification.py +671 -0
- loopx/control_plane/testing/replan_semantic_action_behavior.py +1302 -0
- loopx/control_plane/testing/scoped_gate_successor_tool_behavior.py +527 -0
- loopx/control_plane/testing/selected_todo_tool_behavior.py +1002 -0
- loopx/control_plane/testing/terminal_settlement_tool_behavior.py +656 -0
- loopx/control_plane/todos/__init__.py +1 -0
- loopx/control_plane/todos/active_state_editing.py +296 -0
- loopx/control_plane/todos/active_state_todo_parser.py +138 -0
- loopx/control_plane/todos/active_state_todos.py +175 -0
- loopx/control_plane/todos/addition.py +103 -0
- loopx/control_plane/todos/claim_visibility.py +253 -0
- loopx/control_plane/todos/completed_archive.py +139 -0
- loopx/control_plane/todos/completion_fence.py +49 -0
- loopx/control_plane/todos/completion_policy.py +153 -0
- loopx/control_plane/todos/completion_validation.py +248 -0
- loopx/control_plane/todos/completion_validation_accountability.py +27 -0
- loopx/control_plane/todos/completion_validation_projection.py +57 -0
- loopx/control_plane/todos/contract.py +1476 -0
- loopx/control_plane/todos/decision_scope.py +554 -0
- loopx/control_plane/todos/deferred_resume.py +546 -0
- loopx/control_plane/todos/durable_completion.py +201 -0
- loopx/control_plane/todos/event_writeback.py +484 -0
- loopx/control_plane/todos/frontier_deadline.py +132 -0
- loopx/control_plane/todos/handoff_gate.py +283 -0
- loopx/control_plane/todos/handoff_mode.py +444 -0
- loopx/control_plane/todos/handoff_note.py +202 -0
- loopx/control_plane/todos/line_update.py +361 -0
- loopx/control_plane/todos/list_projection.py +205 -0
- loopx/control_plane/todos/markdown.py +199 -0
- loopx/control_plane/todos/monitor_metadata.py +88 -0
- loopx/control_plane/todos/mutation_authority.py +299 -0
- loopx/control_plane/todos/projection.py +655 -0
- loopx/control_plane/todos/quota_summary.py +1138 -0
- loopx/control_plane/todos/route_continuation.py +267 -0
- loopx/control_plane/todos/succession_warning.py +174 -0
- loopx/control_plane/todos/summary_item.py +223 -0
- loopx/control_plane/todos/text.py +30 -0
- loopx/control_plane/todos/todo_index.py +226 -0
- loopx/control_plane/todos/todo_summary.py +1458 -0
- loopx/control_plane/todos/unblock_resume.py +326 -0
- loopx/control_plane/todos/user_gate.py +263 -0
- loopx/control_plane/todos/write_hint.py +63 -0
- loopx/control_plane/todos/write_policy.py +135 -0
- loopx/control_plane/turn_driver/__init__.py +85 -0
- loopx/control_plane/turn_driver/codex_cli.py +502 -0
- loopx/control_plane/turn_driver/driver.py +355 -0
- loopx/control_plane/turn_driver/executor.py +1468 -0
- loopx/control_plane/turn_driver/loop_controller.py +669 -0
- loopx/control_plane/turn_driver/settlement.py +318 -0
- loopx/control_plane/turn_driver/transaction.py +375 -0
- loopx/control_plane/work_items/__init__.py +1 -0
- loopx/control_plane/work_items/attention_fields.py +56 -0
- loopx/control_plane/work_items/attention_item.py +77 -0
- loopx/control_plane/work_items/attention_queue.py +322 -0
- loopx/control_plane/work_items/attention_routing.py +213 -0
- loopx/control_plane/work_items/autonomous_candidates.py +135 -0
- loopx/control_plane/work_items/autonomous_replan_ack.py +276 -0
- loopx/control_plane/work_items/autonomous_replan_obligation.py +786 -0
- loopx/control_plane/work_items/backlog_hygiene.py +59 -0
- loopx/control_plane/work_items/capability_monitor_fallback.py +221 -0
- loopx/control_plane/work_items/delivery_batch_scale.py +66 -0
- loopx/control_plane/work_items/delivery_outcome.py +152 -0
- loopx/control_plane/work_items/delivery_signals.py +113 -0
- loopx/control_plane/work_items/execution_obligation.py +235 -0
- loopx/control_plane/work_items/goal_route_hint.py +320 -0
- loopx/control_plane/work_items/interaction_contract.py +1540 -0
- loopx/control_plane/work_items/issue_meta_surface.py +159 -0
- loopx/control_plane/work_items/lifecycle.py +139 -0
- loopx/control_plane/work_items/operator_inbox.py +266 -0
- loopx/control_plane/work_items/outcome_followthrough.py +69 -0
- loopx/control_plane/work_items/primary_action.py +326 -0
- loopx/control_plane/work_items/progress_observation.py +630 -0
- loopx/control_plane/work_items/project_asset.py +675 -0
- loopx/control_plane/work_items/repair_delta.py +693 -0
- loopx/control_plane/work_items/runtime_capability_reentry.py +168 -0
- loopx/control_plane/work_items/semantic_replan_writeback.py +177 -0
- loopx/control_plane/work_items/status_contract.py +49 -0
- loopx/control_plane/work_items/task_graph.py +1046 -0
- loopx/control_plane/work_items/task_lease.py +1254 -0
- loopx/control_plane/work_items/task_lease_settlement.py +422 -0
- loopx/control_plane/work_items/work_lane.py +510 -0
- loopx/control_plane/work_items/work_lane_context.py +161 -0
- loopx/demo.py +247 -0
- loopx/diagnose.py +633 -0
- loopx/doctor.py +1251 -0
- loopx/domain_packs/__init__.py +1 -0
- loopx/domain_packs/issue_fix.py +571 -0
- loopx/domain_packs/ml_experiment.py +854 -0
- loopx/domain_state.py +137 -0
- loopx/dreaming.py +706 -0
- loopx/entrypoint.py +16 -0
- loopx/event_sourced_state.py +981 -0
- loopx/execution_profile.py +286 -0
- loopx/experiments/__init__.py +1 -0
- loopx/experiments/planner_worker/__init__.py +1 -0
- loopx/experiments/planner_worker/contract.py +523 -0
- loopx/experiments/planner_worker/runtime.py +391 -0
- loopx/experiments/planner_worker/traex.py +461 -0
- loopx/explore_graph.py +11 -0
- loopx/extensions/__init__.py +1 -0
- loopx/extensions/bundled.py +28 -0
- loopx/extensions/execution_envelope.py +126 -0
- loopx/extensions/lark/__init__.py +11 -0
- loopx/extensions/lark/event_collector.py +478 -0
- loopx/extensions/lark/event_collector_runtime.py +506 -0
- loopx/extensions/lark/event_inbox.py +454 -0
- loopx/extensions/lark/extension.toml +88 -0
- loopx/extensions/lark/goal_channel.py +44 -0
- loopx/extensions/lark/goal_channel_contracts.py +388 -0
- loopx/extensions/lark/goal_channel_lifecycle.py +218 -0
- loopx/extensions/lark/goal_channel_runtime.py +792 -0
- loopx/extensions/lark/goal_channel_setup.py +805 -0
- loopx/extensions/lark/goal_channel_targets.py +215 -0
- loopx/extensions/lark/goal_channel_transport.py +281 -0
- loopx/extensions/lark/inbox_reactions.py +650 -0
- loopx/extensions/lark/inbox_reply.py +430 -0
- loopx/extensions/lark/presentation/__init__.py +11 -0
- loopx/extensions/lark/presentation/explore_results.py +2276 -0
- loopx/extensions/lark/presentation/explore_singleflight.py +127 -0
- loopx/extensions/lark/presentation/explore_source_guard.py +121 -0
- loopx/extensions/lark/presentation/explore_stage_document.py +703 -0
- loopx/extensions/lark/presentation/explore_visual_integrity.py +122 -0
- loopx/extensions/lark/presentation/explore_visual_readback.py +452 -0
- loopx/extensions/lark/presentation/explore_visual_styles.py +156 -0
- loopx/extensions/lark/presentation/issue_fix_surface.py +612 -0
- loopx/extensions/lark/presentation/kanban.py +2791 -0
- loopx/extensions/lark/presentation/message_card.py +112 -0
- loopx/extensions/lark/presentation/periodic_report.py +261 -0
- loopx/extensions/lark/presentation/projection_rows.py +600 -0
- loopx/extensions/lark/presentation/record_io.py +95 -0
- loopx/extensions/lark/presentation/sync_receipt.py +145 -0
- loopx/extensions/lark/private_json.py +40 -0
- loopx/extensions/lark/provider.py +86 -0
- loopx/extensions/lark/reviewer_notification.py +604 -0
- loopx/extensions/manifest.py +385 -0
- loopx/extensions/openviking_periodic_report/__init__.py +17 -0
- loopx/extensions/openviking_periodic_report/activation.py +173 -0
- loopx/extensions/openviking_periodic_report/extension.toml +17 -0
- loopx/extensions/openviking_periodic_report/provider.py +355 -0
- loopx/extensions/openviking_periodic_report/sink.py +117 -0
- loopx/extensions/openviking_semantic_preference/__init__.py +5 -0
- loopx/extensions/openviking_semantic_preference/extension.toml +16 -0
- loopx/extensions/openviking_semantic_preference/history_export.py +484 -0
- loopx/extensions/openviking_semantic_preference/project_peer.py +68 -0
- loopx/extensions/openviking_semantic_preference/provider.py +312 -0
- loopx/extensions/presentation.py +979 -0
- loopx/extensions/process_runtime.py +204 -0
- loopx/extensions/readiness.py +168 -0
- loopx/extensions/runtime.py +931 -0
- loopx/extensions/scaffold.py +335 -0
- loopx/feedback.py +581 -0
- loopx/file_lock.py +382 -0
- loopx/global_registry.py +842 -0
- loopx/global_risks.py +970 -0
- loopx/global_todos.py +568 -0
- loopx/handoff_budget.py +28 -0
- loopx/heartbeat_prequota.py +80 -0
- loopx/heartbeat_prompt.py +159 -0
- loopx/help_surface.py +516 -0
- loopx/history.py +1507 -0
- loopx/host_loop_activation.py +1311 -0
- loopx/host_mode_planner.py +991 -0
- loopx/install_contract.py +1 -0
- loopx/interface_budget.py +196 -0
- loopx/long_task_cadence.py +208 -0
- loopx/materials.py +185 -0
- loopx/ml_experiment.py +3 -0
- loopx/onboarding.py +214 -0
- loopx/opencode2_goal_mode/README.md +81 -0
- loopx/opencode2_goal_mode/__init__.py +9 -0
- loopx/opencode2_goal_mode/opencode2-goal-worker.mjs +1018 -0
- loopx/opencode_goal_mode/README.md +99 -0
- loopx/opencode_goal_mode/__init__.py +13 -0
- loopx/opencode_goal_mode/goal-bridge-runtime.mjs +858 -0
- loopx/opencode_goal_mode/loopx-goal.js +8 -0
- loopx/operator_gate.py +420 -0
- loopx/orchestration.py +127 -0
- loopx/paths.py +59 -0
- loopx/pi_goal_mode/README.md +67 -0
- loopx/pi_goal_mode/__init__.py +13 -0
- loopx/pi_goal_mode/loopx-goal.ts +254 -0
- loopx/pi_goal_mode/pi-goal-loop-runtime.mjs +574 -0
- loopx/pr_review.py +1206 -0
- loopx/presentation/__init__.py +1 -0
- loopx/presentation/explore_views.py +1334 -0
- loopx/presentation/markdown.py +61 -0
- loopx/presentation/projection_source_reconcile.py +140 -0
- loopx/presentation/public_safety.py +42 -0
- loopx/presentation/renderers/__init__.py +17 -0
- loopx/presentation/renderers/goal_channel_html.py +269 -0
- loopx/presentation/renderers/periodic_report_html.py +786 -0
- loopx/presentation/renderers/periodic_report_markdown.py +184 -0
- loopx/presentation/renderers/quota_event_markdown.py +116 -0
- loopx/presentation/renderers/quota_markdown.py +1112 -0
- loopx/presentation/renderers/status_markdown.py +1570 -0
- loopx/presentation/renderers/trajectory_hygiene_markdown.py +39 -0
- loopx/presentation/renderers/turn_envelope_markdown.py +33 -0
- loopx/presentation/sinks/__init__.py +5 -0
- loopx/presentation/sinks/openviking_periodic_report.py +7 -0
- loopx/presentation/static_site.py +691 -0
- loopx/presets.py +369 -0
- loopx/project_alias.py +217 -0
- loopx/project_map.py +589 -0
- loopx/project_prompt.py +1153 -0
- loopx/project_skill_cli.py +125 -0
- loopx/project_skill_delivery.py +470 -0
- loopx/project_uninstall.py +462 -0
- loopx/promotion_gate.py +197 -0
- loopx/quota.py +1197 -0
- loopx/ready_score.py +413 -0
- loopx/registry.py +621 -0
- loopx/registry_writability.py +64 -0
- loopx/release_candidate.py +148 -0
- loopx/release_manifest.py +316 -0
- loopx/repository_identity.py +100 -0
- loopx/review_packet.py +1024 -0
- loopx/rollout_event_log.py +505 -0
- loopx/runtime.py +112 -0
- loopx/self_update.py +750 -0
- loopx/session_runtime.py +418 -0
- loopx/skill_install_readback.py +500 -0
- loopx/slash_command_install.py +1393 -0
- loopx/slash_commands.py +264 -0
- loopx/state_backup.py +573 -0
- loopx/state_migration.py +350 -0
- loopx/state_projection.py +809 -0
- loopx/state_refresh.py +1416 -0
- loopx/status.py +1383 -0
- loopx/status_server.py +935 -0
- loopx/summary_all.py +725 -0
- loopx/terminal_bench_agent.py +2056 -0
- loopx/thread_agent_binding.py +408 -0
- loopx/todo_followups.py +168 -0
- loopx/todo_suggestion_prompt.py +204 -0
- loopx/todos.py +2229 -0
- loopx/turn_identity.py +17 -0
- loopx/upgrade.py +1083 -0
- loopx/visible_governance.py +667 -0
- loopx/visible_multi_agent_launcher.py +1253 -0
- loopx/visible_multi_agent_tmux.py +429 -0
- loopx/worker_bridge.py +1574 -0
- loopx-0.4.8.dist-info/METADATA +708 -0
- loopx-0.4.8.dist-info/RECORD +811 -0
- loopx-0.4.8.dist-info/WHEEL +5 -0
- loopx-0.4.8.dist-info/entry_points.txt +5 -0
- loopx-0.4.8.dist-info/licenses/LICENSE +202 -0
- loopx-0.4.8.dist-info/licenses/LICENSE-MIT +21 -0
- loopx-0.4.8.dist-info/licenses/NOTICE +6 -0
- loopx-0.4.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// <!-- loopx-managed-slash-command:v1 command=/loopx surface=pi-extension-runtime -->
|
|
2
|
+
//
|
|
3
|
+
// LoopX Pi goal loop runtime — the pure, directly executable core of the Pi
|
|
4
|
+
// host adapter. The installed extension (loopx-goal.ts) wires Pi's
|
|
5
|
+
// ExtensionAPI into createGoalLoop; node:test drives this same module through
|
|
6
|
+
// injected dependencies (tests/pi_goal_loop_runtime.test.mjs), so the
|
|
7
|
+
// store/quota/wait lifecycle is verified at runtime instead of by string
|
|
8
|
+
// matching.
|
|
9
|
+
//
|
|
10
|
+
// Lifecycle contract:
|
|
11
|
+
// - Only LoopX-derived terminal state stops auto-continuation; the loop never
|
|
12
|
+
// self-declares closure.
|
|
13
|
+
// - Quota probe failures fail closed with a bounded retry instead of guessing.
|
|
14
|
+
// - dispose() atomically invalidates the extension instance: every timer is
|
|
15
|
+
// cancelled, and an evaluation that is mid-flight across an await returns to
|
|
16
|
+
// an epoch guard before any write / notify / send / timer side effect, so it
|
|
17
|
+
// can never continue the old session past a reload / session-replacement
|
|
18
|
+
// boundary.
|
|
19
|
+
// - Goal identity is a binding contract: every binding carries a per-session
|
|
20
|
+
// generation that activate() increments, and every evaluation write commits
|
|
21
|
+
// through the store's compare-and-swap (expected generation + goalId). A
|
|
22
|
+
// stale evaluation whose write was already in-flight when the same session
|
|
23
|
+
// activated a new goal is rejected at the store commit boundary, so it can
|
|
24
|
+
// never merge old terminal/scheduler state or send the old task body.
|
|
25
|
+
// - Sessions without a session file (pi --no-session, ephemeral) use a unique
|
|
26
|
+
// in-memory identity per extension instance and are never persisted, so a
|
|
27
|
+
// later run cannot inherit the previous run's binding.
|
|
28
|
+
import { createHash, randomUUID } from "node:crypto"
|
|
29
|
+
import { promises as fs } from "node:fs"
|
|
30
|
+
import path from "node:path"
|
|
31
|
+
|
|
32
|
+
export const BRIDGE_SCHEMA_VERSION = "loopx_pi_goal_bridge_v0"
|
|
33
|
+
export const TERMINAL_STATE_SCHEMA_VERSION = "goal_terminal_state_v0"
|
|
34
|
+
export const SOURCE_COMPLETENESS_SCHEMA_VERSION = "goal_terminal_source_completeness_v0"
|
|
35
|
+
export const DEFAULT_RETRY_MINUTES = 3
|
|
36
|
+
|
|
37
|
+
// The label prefix of a session key reserves room for the digest suffix so
|
|
38
|
+
// that createBindingStore's filename sanitization (160 chars) can never cut
|
|
39
|
+
// the digest off: label(48) + "-" + digest(16) = 65 chars.
|
|
40
|
+
const SESSION_KEY_LABEL_MAX = 48
|
|
41
|
+
const SESSION_KEY_DIGEST_LENGTH = 16
|
|
42
|
+
|
|
43
|
+
export function sanitizedKey(value) {
|
|
44
|
+
const cleaned = String(value || "")
|
|
45
|
+
.replace(/[^A-Za-z0-9_-]/g, "_")
|
|
46
|
+
.slice(0, 160)
|
|
47
|
+
return cleaned || "session"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Collision-resistant session key from the full session file path. Uses a
|
|
51
|
+
// short human-readable basename prefix plus a SHA-256 digest of the complete
|
|
52
|
+
// path, so two files whose first 161 bytes are identical never produce the
|
|
53
|
+
// same durable key and the digest always survives filename sanitization.
|
|
54
|
+
export function sessionKey(sessionFile) {
|
|
55
|
+
const digest = createHash("sha256")
|
|
56
|
+
.update(sessionFile)
|
|
57
|
+
.digest("hex")
|
|
58
|
+
.slice(0, SESSION_KEY_DIGEST_LENGTH)
|
|
59
|
+
const label = sanitizedKey(path.basename(sessionFile)).slice(0, SESSION_KEY_LABEL_MAX)
|
|
60
|
+
return label ? `${label}-${digest}` : `session-${digest}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function stateRoot(directory) {
|
|
64
|
+
if (process.env.LOOPX_PI_STATE_DIR) {
|
|
65
|
+
return path.resolve(process.env.LOOPX_PI_STATE_DIR)
|
|
66
|
+
}
|
|
67
|
+
return path.join(directory, ".loopx", "pi")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function bindingDefaults(directory) {
|
|
71
|
+
return {
|
|
72
|
+
schemaVersion: BRIDGE_SCHEMA_VERSION,
|
|
73
|
+
directory,
|
|
74
|
+
goalId: "",
|
|
75
|
+
agentId: "",
|
|
76
|
+
registryPath: "",
|
|
77
|
+
availableCapabilities: [],
|
|
78
|
+
taskBody: "",
|
|
79
|
+
autoResume: true,
|
|
80
|
+
terminal: false,
|
|
81
|
+
generation: 0,
|
|
82
|
+
schedulerToken: "",
|
|
83
|
+
unchangedPolls: 0,
|
|
84
|
+
lastInjectedPrompt: "",
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function casMatches(current, expected) {
|
|
89
|
+
if (!expected) return true
|
|
90
|
+
if (expected.generation !== undefined && (current?.generation || 0) !== expected.generation) {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
if (expected.goalId !== undefined && (current?.goalId || "") !== expected.goalId) {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// File-backed binding store scoped to one project. Bindings live under the
|
|
100
|
+
// gitignored `.loopx/` tree so they survive Pi restarts without touching
|
|
101
|
+
// tracked repository state. Writes for one key are serialized through a queue,
|
|
102
|
+
// and the optional `expected` argument turns a write into a compare-and-swap:
|
|
103
|
+
// the commit is rejected (returns null) unless the persisted generation and
|
|
104
|
+
// goalId still match, so a stale evaluation can never overwrite a newly
|
|
105
|
+
// activated binding.
|
|
106
|
+
export function createBindingStore(directory) {
|
|
107
|
+
const root = stateRoot(directory)
|
|
108
|
+
const target = (key) => path.join(root, `${sanitizedKey(key)}.json`)
|
|
109
|
+
const queues = new Map()
|
|
110
|
+
const enqueue = (key, task) => {
|
|
111
|
+
const previous = queues.get(key) || Promise.resolve()
|
|
112
|
+
const next = previous.then(task)
|
|
113
|
+
queues.set(
|
|
114
|
+
key,
|
|
115
|
+
next.then(
|
|
116
|
+
() => {},
|
|
117
|
+
() => {},
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
return next
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
async read(key) {
|
|
124
|
+
try {
|
|
125
|
+
const payload = JSON.parse(await fs.readFile(target(key), "utf8"))
|
|
126
|
+
if (payload?.schemaVersion !== BRIDGE_SCHEMA_VERSION || payload?.sessionKey !== key) {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
return payload
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error?.code === "ENOENT") return null
|
|
132
|
+
throw error
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
async write(key, changes, expected) {
|
|
136
|
+
return enqueue(key, async () => {
|
|
137
|
+
const current = await this.read(key)
|
|
138
|
+
if (!casMatches(current, expected)) return null
|
|
139
|
+
const payload = {
|
|
140
|
+
...bindingDefaults(directory),
|
|
141
|
+
sessionKey: key,
|
|
142
|
+
...(current || {}),
|
|
143
|
+
...changes,
|
|
144
|
+
updatedAt: new Date().toISOString(),
|
|
145
|
+
}
|
|
146
|
+
await fs.mkdir(root, { recursive: true, mode: 0o700 })
|
|
147
|
+
const destination = target(key)
|
|
148
|
+
const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`
|
|
149
|
+
await fs.writeFile(temporary, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
150
|
+
encoding: "utf8",
|
|
151
|
+
mode: 0o600,
|
|
152
|
+
})
|
|
153
|
+
await fs.rename(temporary, destination)
|
|
154
|
+
return payload
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
async remove(key) {
|
|
158
|
+
return enqueue(key, async () => {
|
|
159
|
+
try {
|
|
160
|
+
await fs.unlink(target(key))
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error?.code !== "ENOENT") throw error
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// In-memory binding store for ephemeral sessions: nothing ever reaches the
|
|
170
|
+
// filesystem, so a `pi --no-session` run cannot leak a binding to a later run.
|
|
171
|
+
// Single-threaded Map access makes the compare-and-swap atomic.
|
|
172
|
+
export function createMemoryBindingStore() {
|
|
173
|
+
const bindings = new Map()
|
|
174
|
+
return {
|
|
175
|
+
bindings,
|
|
176
|
+
async read(key) {
|
|
177
|
+
return bindings.get(key) || null
|
|
178
|
+
},
|
|
179
|
+
async write(key, changes, expected) {
|
|
180
|
+
const current = bindings.get(key) || {}
|
|
181
|
+
if (!casMatches(current, expected)) return null
|
|
182
|
+
const payload = {
|
|
183
|
+
...bindingDefaults(""),
|
|
184
|
+
sessionKey: key,
|
|
185
|
+
...current,
|
|
186
|
+
...changes,
|
|
187
|
+
}
|
|
188
|
+
bindings.set(key, payload)
|
|
189
|
+
return payload
|
|
190
|
+
},
|
|
191
|
+
async remove(key) {
|
|
192
|
+
bindings.delete(key)
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Per-extension-instance identity for sessions without a session file. Every
|
|
198
|
+
// instance gets a unique key and its own in-memory store, so consecutive
|
|
199
|
+
// `--no-session` runs can never inherit a previous run's binding; the current
|
|
200
|
+
// run must activate the goal again through loopx_goal_activate.
|
|
201
|
+
export function createEphemeralSessionIdentity() {
|
|
202
|
+
return {
|
|
203
|
+
key: `ephemeral-${randomUUID()}`,
|
|
204
|
+
store: createMemoryBindingStore(),
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Mirrors the OpenCode bridge probe: LoopX quota should-run is the only
|
|
209
|
+
// continuation authority for the visible goal loop.
|
|
210
|
+
export function buildQuotaArgs(binding) {
|
|
211
|
+
const args = []
|
|
212
|
+
if (binding.registryPath) args.push("--registry", binding.registryPath)
|
|
213
|
+
args.push(
|
|
214
|
+
"--format",
|
|
215
|
+
"json",
|
|
216
|
+
"quota",
|
|
217
|
+
"should-run",
|
|
218
|
+
"--goal-id",
|
|
219
|
+
binding.goalId,
|
|
220
|
+
"--runtime-profile",
|
|
221
|
+
"generic_cli",
|
|
222
|
+
"--include-detail",
|
|
223
|
+
"scheduler",
|
|
224
|
+
)
|
|
225
|
+
if (binding.agentId) args.push("--agent-id", binding.agentId)
|
|
226
|
+
for (const capability of binding.availableCapabilities || []) {
|
|
227
|
+
args.push("--available-capability", capability)
|
|
228
|
+
}
|
|
229
|
+
return args
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function isTerminalNoFollowup(decision) {
|
|
233
|
+
const frontier = decision?.goal_frontier_projection
|
|
234
|
+
const terminal = frontier?.terminal_state
|
|
235
|
+
const completeness = frontier?.source_completeness
|
|
236
|
+
return Boolean(
|
|
237
|
+
decision?.should_run === false &&
|
|
238
|
+
decision?.effective_action === "terminal_no_followup" &&
|
|
239
|
+
terminal?.schema_version === TERMINAL_STATE_SCHEMA_VERSION &&
|
|
240
|
+
terminal?.kind === "no_followup" &&
|
|
241
|
+
terminal?.derived === true &&
|
|
242
|
+
terminal?.source === "validated_goal_closure" &&
|
|
243
|
+
completeness?.schema_version === SOURCE_COMPLETENESS_SCHEMA_VERSION &&
|
|
244
|
+
completeness?.user_todos === "valid" &&
|
|
245
|
+
completeness?.agent_todos === "valid",
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function shouldRunNow(decision) {
|
|
250
|
+
const hint = decision?.scheduler_hint
|
|
251
|
+
return hint?.action === "run_now" || decision?.should_run === true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function waitPlan(decision, binding) {
|
|
255
|
+
const hint = decision?.scheduler_hint || {}
|
|
256
|
+
const unchanged = hint?.unchanged_poll || {}
|
|
257
|
+
const local = unchanged?.local_scheduler
|
|
258
|
+
if (!local || local === "stop") {
|
|
259
|
+
return {
|
|
260
|
+
stop: true,
|
|
261
|
+
minutes: DEFAULT_RETRY_MINUTES,
|
|
262
|
+
schedulerToken: binding.schedulerToken,
|
|
263
|
+
unchangedPolls: binding.unchangedPolls,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const reset = hint?.reset_policy || {}
|
|
267
|
+
const token = String(reset?.reset_token || "")
|
|
268
|
+
const sameIdentity = Boolean(token) && token === binding.schedulerToken
|
|
269
|
+
const unchangedPolls = sameIdentity ? Number(binding.unchangedPolls || 0) : 0
|
|
270
|
+
const limit = Number.isInteger(local.unchanged_poll_limit)
|
|
271
|
+
? Number(local.unchanged_poll_limit)
|
|
272
|
+
: null
|
|
273
|
+
if (limit !== null && unchangedPolls >= limit) {
|
|
274
|
+
return {
|
|
275
|
+
stop: true,
|
|
276
|
+
minutes: DEFAULT_RETRY_MINUTES,
|
|
277
|
+
schedulerToken: token,
|
|
278
|
+
unchangedPolls,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const progression = Array.isArray(local.example_progression_minutes)
|
|
282
|
+
? local.example_progression_minutes.filter((value) => Number(value) > 0)
|
|
283
|
+
: []
|
|
284
|
+
const fallback = Number(local.recommended_interval_minutes || DEFAULT_RETRY_MINUTES)
|
|
285
|
+
const minutes = progression.length
|
|
286
|
+
? Number(progression[Math.min(unchangedPolls, progression.length - 1)])
|
|
287
|
+
: fallback
|
|
288
|
+
return {
|
|
289
|
+
stop: false,
|
|
290
|
+
minutes: Number.isFinite(minutes) && minutes > 0 ? minutes : DEFAULT_RETRY_MINUTES,
|
|
291
|
+
schedulerToken: token,
|
|
292
|
+
unchangedPolls: unchangedPolls + 1,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// The quota-gated auto-continuation loop for one extension instance.
|
|
297
|
+
//
|
|
298
|
+
// options:
|
|
299
|
+
// quotaProbe(binding) async LoopX quota should-run probe
|
|
300
|
+
// sendMessage(prompt) inject the heartbeat task body as a follow-up
|
|
301
|
+
// setTimer(cb, delayMs) schedule a timer, returns an opaque handle
|
|
302
|
+
// clearTimer(handle) cancel a scheduled timer
|
|
303
|
+
//
|
|
304
|
+
// The loop keeps per-session timers and evaluations but owns one instance-wide
|
|
305
|
+
// epoch, so session shutdown atomically invalidates every session's scheduled
|
|
306
|
+
// and in-flight work at once. Goal identity is enforced through the binding's
|
|
307
|
+
// persisted generation: activate() increments it and every evaluation write
|
|
308
|
+
// commits via the store's compare-and-swap with the captured generation and
|
|
309
|
+
// goalId, so a stale evaluation cannot commit past a re-activation even when
|
|
310
|
+
// its write was already in-flight.
|
|
311
|
+
export function createGoalLoop(options) {
|
|
312
|
+
const { quotaProbe, sendMessage, setTimer, clearTimer } = options
|
|
313
|
+
const timers = new Map()
|
|
314
|
+
const evaluations = new Map()
|
|
315
|
+
const contexts = new Map()
|
|
316
|
+
let disposed = false
|
|
317
|
+
let epoch = 0
|
|
318
|
+
|
|
319
|
+
const cancelScheduled = (key) => {
|
|
320
|
+
const timer = timers.get(key)
|
|
321
|
+
if (timer !== undefined) clearTimer(timer)
|
|
322
|
+
timers.delete(key)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const cancelAll = () => {
|
|
326
|
+
for (const timer of timers.values()) clearTimer(timer)
|
|
327
|
+
timers.clear()
|
|
328
|
+
evaluations.clear()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const scheduleEvaluation = (key, minutes) => {
|
|
332
|
+
if (disposed) return
|
|
333
|
+
cancelScheduled(key)
|
|
334
|
+
const scheduledEpoch = epoch
|
|
335
|
+
const timer = setTimer(async () => {
|
|
336
|
+
timers.delete(key)
|
|
337
|
+
if (disposed || epoch !== scheduledEpoch) return
|
|
338
|
+
try {
|
|
339
|
+
await evaluateIdle(key)
|
|
340
|
+
} catch {
|
|
341
|
+
// Evaluation must never crash the host; fail closed with a retry.
|
|
342
|
+
scheduleEvaluation(key, DEFAULT_RETRY_MINUTES)
|
|
343
|
+
}
|
|
344
|
+
}, Math.max(1, minutes) * 60_000)
|
|
345
|
+
timers.set(key, timer)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const evaluateIdleOnce = async (key) => {
|
|
349
|
+
if (disposed) return
|
|
350
|
+
const instanceEpoch = epoch
|
|
351
|
+
const services = contexts.get(key)
|
|
352
|
+
if (!services) {
|
|
353
|
+
cancelScheduled(key)
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
const { store, isIdle } = services
|
|
357
|
+
// Every store write inside this evaluation can throw (disk error,
|
|
358
|
+
// permission denied). The timer callback already reschedules on error;
|
|
359
|
+
// this top-level catch gives the direct evaluation path the same
|
|
360
|
+
// fail-closed-with-retry contract.
|
|
361
|
+
try {
|
|
362
|
+
let binding = null
|
|
363
|
+
try {
|
|
364
|
+
binding = await store.read(key)
|
|
365
|
+
} catch {
|
|
366
|
+
cancelScheduled(key)
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
370
|
+
if (!binding || binding.terminal) {
|
|
371
|
+
cancelScheduled(key)
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
if (binding.autoResume === false || !isIdle()) {
|
|
375
|
+
cancelScheduled(key)
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Capture the binding identity so every commit below can CAS against it:
|
|
380
|
+
// a re-activation that happens while this evaluation is in-flight bumps
|
|
381
|
+
// the persisted generation and rejects the stale commit.
|
|
382
|
+
const capturedGen = binding.generation || 0
|
|
383
|
+
const capturedGoalId = binding.goalId
|
|
384
|
+
const expected = { generation: capturedGen, goalId: capturedGoalId }
|
|
385
|
+
|
|
386
|
+
let decision
|
|
387
|
+
try {
|
|
388
|
+
decision = await quotaProbe(binding)
|
|
389
|
+
} catch {
|
|
390
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
391
|
+
// Fail closed: never continue without LoopX authority. Bounded retry.
|
|
392
|
+
scheduleEvaluation(key, DEFAULT_RETRY_MINUTES)
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
396
|
+
|
|
397
|
+
const current = await store.read(key)
|
|
398
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
399
|
+
if (
|
|
400
|
+
!current ||
|
|
401
|
+
current.terminal ||
|
|
402
|
+
current.autoResume === false ||
|
|
403
|
+
current.generation !== capturedGen ||
|
|
404
|
+
current.goalId !== capturedGoalId
|
|
405
|
+
) {
|
|
406
|
+
cancelScheduled(key)
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (isTerminalNoFollowup(decision)) {
|
|
411
|
+
// Commit through the store's compare-and-swap: if the same session
|
|
412
|
+
// activated a new goal while this write was in-flight, the commit is
|
|
413
|
+
// rejected and the new binding stays alive.
|
|
414
|
+
const committed = await store.write(key, { terminal: true, autoResume: false }, expected)
|
|
415
|
+
if (!committed) {
|
|
416
|
+
cancelScheduled(key)
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
420
|
+
cancelScheduled(key)
|
|
421
|
+
services.notify(
|
|
422
|
+
`LoopX goal ${current.goalId} reached validated terminal no-follow-up; loop stopped.`,
|
|
423
|
+
"info",
|
|
424
|
+
)
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (shouldRunNow(decision)) {
|
|
429
|
+
cancelScheduled(key)
|
|
430
|
+
const schedulerCommit = await store.write(
|
|
431
|
+
key,
|
|
432
|
+
{ schedulerToken: "", unchangedPolls: 0 },
|
|
433
|
+
expected,
|
|
434
|
+
)
|
|
435
|
+
if (!schedulerCommit) {
|
|
436
|
+
cancelScheduled(key)
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
440
|
+
const prompt = current.taskBody || current.goalId
|
|
441
|
+
const promptCommit = await store.write(key, { lastInjectedPrompt: prompt }, expected)
|
|
442
|
+
if (!promptCommit) {
|
|
443
|
+
cancelScheduled(key)
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
447
|
+
sendMessage(prompt)
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const wait = waitPlan(decision, current)
|
|
452
|
+
if (wait.stop) {
|
|
453
|
+
cancelScheduled(key)
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
const waitCommit = await store.write(
|
|
457
|
+
key,
|
|
458
|
+
{
|
|
459
|
+
schedulerToken: wait.schedulerToken,
|
|
460
|
+
unchangedPolls: wait.unchangedPolls,
|
|
461
|
+
},
|
|
462
|
+
expected,
|
|
463
|
+
)
|
|
464
|
+
if (!waitCommit) {
|
|
465
|
+
cancelScheduled(key)
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
469
|
+
scheduleEvaluation(key, wait.minutes)
|
|
470
|
+
} catch {
|
|
471
|
+
// Fail closed with bounded retry: any unhandled error (store write
|
|
472
|
+
// failure, etc.) must not break the evaluation chain permanently.
|
|
473
|
+
cancelScheduled(key)
|
|
474
|
+
scheduleEvaluation(key, DEFAULT_RETRY_MINUTES)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const evaluateIdle = (key) => {
|
|
479
|
+
if (disposed) return Promise.resolve()
|
|
480
|
+
const existing = evaluations.get(key)
|
|
481
|
+
if (existing) return existing
|
|
482
|
+
const evaluation = evaluateIdleOnce(key).finally(() => {
|
|
483
|
+
if (evaluations.get(key) === evaluation) evaluations.delete(key)
|
|
484
|
+
})
|
|
485
|
+
evaluations.set(key, evaluation)
|
|
486
|
+
return evaluation
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
// Bind the ctx-derived services (store, isIdle, notify) for a session key.
|
|
491
|
+
// Re-binding on every event keeps the loop free of host types while still
|
|
492
|
+
// using the freshest context available. The adapter must pass one stable
|
|
493
|
+
// store instance per key so the store's per-key commit queue is shared.
|
|
494
|
+
bind(key, services) {
|
|
495
|
+
contexts.set(key, services)
|
|
496
|
+
},
|
|
497
|
+
cancel(key) {
|
|
498
|
+
cancelScheduled(key)
|
|
499
|
+
},
|
|
500
|
+
async activate(key, fields) {
|
|
501
|
+
if (disposed) return null
|
|
502
|
+
const services = contexts.get(key)
|
|
503
|
+
if (!services) throw new Error("loopx_goal_activate requires a bound session context")
|
|
504
|
+
// Increment the persisted generation so every in-flight evaluation for
|
|
505
|
+
// this key is rejected at its next compare-and-swap commit.
|
|
506
|
+
const current = await services.store.read(key)
|
|
507
|
+
const generation = (current?.generation || 0) + 1
|
|
508
|
+
const binding = await services.store.write(key, { ...fields, generation })
|
|
509
|
+
if (disposed) return null
|
|
510
|
+
cancelScheduled(key)
|
|
511
|
+
services.notify(
|
|
512
|
+
`LoopX goal ${binding.goalId} activated; continuation gated by LoopX quota.`,
|
|
513
|
+
"info",
|
|
514
|
+
)
|
|
515
|
+
return binding
|
|
516
|
+
},
|
|
517
|
+
async settle(key) {
|
|
518
|
+
if (disposed) return
|
|
519
|
+
const instanceEpoch = epoch
|
|
520
|
+
const services = contexts.get(key)
|
|
521
|
+
if (!services) return
|
|
522
|
+
let binding = null
|
|
523
|
+
try {
|
|
524
|
+
binding = await services.store.read(key)
|
|
525
|
+
} catch {
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
529
|
+
if (!binding || binding.terminal || binding.autoResume === false) return
|
|
530
|
+
await evaluateIdle(key)
|
|
531
|
+
},
|
|
532
|
+
async userPrompt(key, prompt) {
|
|
533
|
+
if (disposed) return
|
|
534
|
+
const instanceEpoch = epoch
|
|
535
|
+
const services = contexts.get(key)
|
|
536
|
+
if (!services) return
|
|
537
|
+
let binding = null
|
|
538
|
+
try {
|
|
539
|
+
binding = await services.store.read(key)
|
|
540
|
+
} catch {
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
544
|
+
if (!binding || binding.terminal) return
|
|
545
|
+
if (String(prompt || "") !== binding.lastInjectedPrompt) {
|
|
546
|
+
const expected = { generation: binding.generation || 0, goalId: binding.goalId }
|
|
547
|
+
const committed = await services.store.write(key, { autoResume: false }, expected)
|
|
548
|
+
if (!committed) return
|
|
549
|
+
if (disposed || epoch !== instanceEpoch) return
|
|
550
|
+
cancelScheduled(key)
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
async resume(key) {
|
|
554
|
+
if (disposed) return null
|
|
555
|
+
const services = contexts.get(key)
|
|
556
|
+
if (!services) return null
|
|
557
|
+
const binding = await services.store.write(key, { autoResume: true, terminal: false })
|
|
558
|
+
if (disposed) return null
|
|
559
|
+
cancelScheduled(key)
|
|
560
|
+
services.notify(`LoopX goal ${binding.goalId} auto-continuation resumed.`, "info")
|
|
561
|
+
return binding
|
|
562
|
+
},
|
|
563
|
+
// Session shutdown / session replacement: atomically invalidate this
|
|
564
|
+
// extension instance and cancel every timer. A quota probe that is
|
|
565
|
+
// already in flight — or an evaluation suspended on any later store
|
|
566
|
+
// await — returns to the epoch guard, so it cannot write, notify, send
|
|
567
|
+
// the old task body, or reschedule a timer past the boundary.
|
|
568
|
+
dispose() {
|
|
569
|
+
disposed = true
|
|
570
|
+
epoch += 1
|
|
571
|
+
cancelAll()
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
}
|