agentic-project-kit 1.0.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.
- agentic_project_kit/__init__.py +1 -0
- agentic_project_kit/absolute_path_portability_audit.py +241 -0
- agentic_project_kit/access_level_policy.py +14 -0
- agentic_project_kit/access_levels.py +22 -0
- agentic_project_kit/action_registry.py +195 -0
- agentic_project_kit/action_specs.py +286 -0
- agentic_project_kit/agent_command_runner.py +516 -0
- agentic_project_kit/audit_test_matrix.py +36 -0
- agentic_project_kit/audit_test_matrix_cli.py +43 -0
- agentic_project_kit/chat_bootloader.py +257 -0
- agentic_project_kit/chat_entrypoint_contract.py +297 -0
- agentic_project_kit/checks.py +511 -0
- agentic_project_kit/ci_readiness.py +211 -0
- agentic_project_kit/cli.py +147 -0
- agentic_project_kit/cli_commands/__init__.py +1 -0
- agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
- agentic_project_kit/cli_commands/actions.py +21 -0
- agentic_project_kit/cli_commands/artifact_gc.py +240 -0
- agentic_project_kit/cli_commands/boot.py +55 -0
- agentic_project_kit/cli_commands/chat.py +34 -0
- agentic_project_kit/cli_commands/checks.py +305 -0
- agentic_project_kit/cli_commands/cockpit.py +103 -0
- agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
- agentic_project_kit/cli_commands/commands.py +129 -0
- agentic_project_kit/cli_commands/dev.py +23 -0
- agentic_project_kit/cli_commands/direction.py +92 -0
- agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
- agentic_project_kit/cli_commands/doc_registry.py +126 -0
- agentic_project_kit/cli_commands/docs.py +223 -0
- agentic_project_kit/cli_commands/dpa.py +1338 -0
- agentic_project_kit/cli_commands/evidence.py +251 -0
- agentic_project_kit/cli_commands/github.py +34 -0
- agentic_project_kit/cli_commands/governance.py +14 -0
- agentic_project_kit/cli_commands/gui.py +23 -0
- agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
- agentic_project_kit/cli_commands/handoff.py +183 -0
- agentic_project_kit/cli_commands/human_workflows.py +430 -0
- agentic_project_kit/cli_commands/init.py +132 -0
- agentic_project_kit/cli_commands/instruction.py +75 -0
- agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
- agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
- agentic_project_kit/cli_commands/pass_already_done.py +66 -0
- agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
- agentic_project_kit/cli_commands/patch_preflight.py +37 -0
- agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
- agentic_project_kit/cli_commands/patterns.py +40 -0
- agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
- agentic_project_kit/cli_commands/pr.py +133 -0
- agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
- agentic_project_kit/cli_commands/profiles.py +26 -0
- agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
- agentic_project_kit/cli_commands/project_direction.py +25 -0
- agentic_project_kit/cli_commands/release.py +392 -0
- agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
- agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
- agentic_project_kit/cli_commands/remote_next.py +57 -0
- agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
- agentic_project_kit/cli_commands/rule_registry.py +129 -0
- agentic_project_kit/cli_commands/rules.py +222 -0
- agentic_project_kit/cli_commands/scaffold.py +46 -0
- agentic_project_kit/cli_commands/slice.py +21 -0
- agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
- agentic_project_kit/cli_commands/state.py +51 -0
- agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
- agentic_project_kit/cli_commands/todo.py +53 -0
- agentic_project_kit/cli_commands/transfer.py +19 -0
- agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
- agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
- agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
- agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
- agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
- agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
- agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
- agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
- agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
- agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
- agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
- agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
- agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
- agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
- agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
- agentic_project_kit/cli_commands/transfer_shared.py +274 -0
- agentic_project_kit/cli_commands/validation.py +200 -0
- agentic_project_kit/cli_commands/version.py +27 -0
- agentic_project_kit/cli_commands/work_orders.py +219 -0
- agentic_project_kit/cli_commands/workflow.py +471 -0
- agentic_project_kit/cli_commands/workflow_guard.py +26 -0
- agentic_project_kit/cli_commands/workspace.py +252 -0
- agentic_project_kit/cockpit.py +608 -0
- agentic_project_kit/cockpit_readiness.py +67 -0
- agentic_project_kit/command_authority_audit.py +192 -0
- agentic_project_kit/command_inbox_check.py +103 -0
- agentic_project_kit/command_manifest.py +591 -0
- agentic_project_kit/command_queue_contract.py +161 -0
- agentic_project_kit/command_selector.py +245 -0
- agentic_project_kit/command_taxonomy.py +264 -0
- agentic_project_kit/commit_guard.py +67 -0
- agentic_project_kit/communication_artifact_gc.py +495 -0
- agentic_project_kit/communication_rule_context.py +437 -0
- agentic_project_kit/communication_state.py +115 -0
- agentic_project_kit/composed_wrapper_invariants.py +97 -0
- agentic_project_kit/composite_short_circuit.py +140 -0
- agentic_project_kit/contract.py +251 -0
- agentic_project_kit/control_state.py +83 -0
- agentic_project_kit/doc_currency_audit.py +536 -0
- agentic_project_kit/doc_lifecycle.py +1179 -0
- agentic_project_kit/doc_lifecycle_signals.py +446 -0
- agentic_project_kit/doc_lifecycle_sweep.py +639 -0
- agentic_project_kit/doc_mesh.py +491 -0
- agentic_project_kit/doctor.py +379 -0
- agentic_project_kit/documentation_registry.py +1122 -0
- agentic_project_kit/documentation_system_audit.py +310 -0
- agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
- agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
- agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
- agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
- agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
- agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
- agentic_project_kit/dpa_final_closeout.py +627 -0
- agentic_project_kit/dpa_fixture_evidence.py +680 -0
- agentic_project_kit/dpa_maintainer_record_check.py +711 -0
- agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
- agentic_project_kit/dpa_probe_002_readiness.py +509 -0
- agentic_project_kit/dpa_probe_003_readiness.py +417 -0
- agentic_project_kit/dpa_probe_004_readiness.py +437 -0
- agentic_project_kit/dpa_readiness.py +505 -0
- agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
- agentic_project_kit/dpa_registry_contracts.py +312 -0
- agentic_project_kit/dpa_renderer_readiness.py +407 -0
- agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
- agentic_project_kit/dpa_stable_readiness.py +743 -0
- agentic_project_kit/dpa_successor_projection.py +168 -0
- agentic_project_kit/dpa_workspace_init_projection.py +93 -0
- agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
- agentic_project_kit/entrypoint_slice_runner.py +153 -0
- agentic_project_kit/evidence_clean.py +152 -0
- agentic_project_kit/evidence_commit_paths.py +214 -0
- agentic_project_kit/evidence_finalize_log.py +204 -0
- agentic_project_kit/evidence_guard.py +114 -0
- agentic_project_kit/evidence_inspector.py +507 -0
- agentic_project_kit/evidence_state_contract.py +99 -0
- agentic_project_kit/execution_mode_state.py +148 -0
- agentic_project_kit/final_summary_contract.py +85 -0
- agentic_project_kit/finalize_guard.py +139 -0
- agentic_project_kit/gatekeeper_core.py +232 -0
- agentic_project_kit/github.py +25 -0
- agentic_project_kit/governance.py +59 -0
- agentic_project_kit/gui_action_execution.py +105 -0
- agentic_project_kit/gui_action_renderer.py +81 -0
- agentic_project_kit/gui_action_run_intent.py +98 -0
- agentic_project_kit/gui_action_views.py +195 -0
- agentic_project_kit/gui_activity_log.py +76 -0
- agentic_project_kit/gui_button_catalog.py +605 -0
- agentic_project_kit/gui_cockpit.py +365 -0
- agentic_project_kit/gui_cockpit_actions.py +695 -0
- agentic_project_kit/gui_cockpit_common.py +172 -0
- agentic_project_kit/gui_cockpit_header.py +501 -0
- agentic_project_kit/gui_cockpit_sidebar.py +387 -0
- agentic_project_kit/gui_cockpit_task.py +748 -0
- agentic_project_kit/gui_command_projection.py +355 -0
- agentic_project_kit/gui_communication_modes.py +130 -0
- agentic_project_kit/gui_dry_run.py +219 -0
- agentic_project_kit/gui_gatekeeper_status.py +259 -0
- agentic_project_kit/gui_layout_plan.py +148 -0
- agentic_project_kit/gui_open_folder.py +18 -0
- agentic_project_kit/gui_output_status_panel.py +61 -0
- agentic_project_kit/gui_panel_state.py +47 -0
- agentic_project_kit/gui_presenter.py +62 -0
- agentic_project_kit/gui_readiness_gate.py +206 -0
- agentic_project_kit/gui_release_flow.py +59 -0
- agentic_project_kit/gui_task_editor.py +1077 -0
- agentic_project_kit/gui_tk_widgets.py +177 -0
- agentic_project_kit/gui_tkinter_renderer.py +87 -0
- agentic_project_kit/gui_tkinter_shell.py +1176 -0
- agentic_project_kit/gui_transfer_contract.py +28 -0
- agentic_project_kit/gui_viewmodel.py +698 -0
- agentic_project_kit/gui_window_guard.py +46 -0
- agentic_project_kit/handoff_freshness.py +390 -0
- agentic_project_kit/handoff_prompt.py +180 -0
- agentic_project_kit/handoff_state.py +254 -0
- agentic_project_kit/instruction_lint.py +455 -0
- agentic_project_kit/llm_context_carriers.py +66 -0
- agentic_project_kit/llm_execution_context.py +285 -0
- agentic_project_kit/local_command_stack.py +133 -0
- agentic_project_kit/local_feature_gate.py +82 -0
- agentic_project_kit/local_garbage_collector.py +269 -0
- agentic_project_kit/models.py +18 -0
- agentic_project_kit/mutation_lock_audit.py +421 -0
- agentic_project_kit/next_turn_evidence.py +253 -0
- agentic_project_kit/next_turn_merge_if_green.py +459 -0
- agentic_project_kit/next_turn_pr_status.py +255 -0
- agentic_project_kit/next_turn_result.py +183 -0
- agentic_project_kit/next_turn_runner.py +159 -0
- agentic_project_kit/next_turn_slot.py +107 -0
- agentic_project_kit/next_turn_status.py +274 -0
- agentic_project_kit/ns_legacy_reference_audit.py +258 -0
- agentic_project_kit/ns_slice_runner.py +16 -0
- agentic_project_kit/operational_handoff_projection.py +124 -0
- agentic_project_kit/output_contract.py +80 -0
- agentic_project_kit/output_repair.py +61 -0
- agentic_project_kit/pass_already_done.py +216 -0
- agentic_project_kit/patch_artifact_preflight.py +147 -0
- agentic_project_kit/patch_cycle_workflow.py +440 -0
- agentic_project_kit/patch_failure_discipline_audit.py +223 -0
- agentic_project_kit/patch_preflight.py +215 -0
- agentic_project_kit/path_literal_audit.py +612 -0
- agentic_project_kit/patterns.py +99 -0
- agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
- agentic_project_kit/portability_shell_gate.py +40 -0
- agentic_project_kit/post_merge_handoff_refresh.py +56 -0
- agentic_project_kit/post_release.py +252 -0
- agentic_project_kit/post_release_closeout.py +616 -0
- agentic_project_kit/pr_cleanup.py +172 -0
- agentic_project_kit/pr_closeout.py +50 -0
- agentic_project_kit/pr_closeout_complete.py +344 -0
- agentic_project_kit/pr_create_or_skip.py +71 -0
- agentic_project_kit/pr_hygiene.py +210 -0
- agentic_project_kit/program_redundancy_audit.py +251 -0
- agentic_project_kit/project_direction.py +840 -0
- agentic_project_kit/protected_change_planner.py +194 -0
- agentic_project_kit/release.py +401 -0
- agentic_project_kit/release_doi_safety.py +24 -0
- agentic_project_kit/release_gate_core.py +174 -0
- agentic_project_kit/release_metadata_authority_gate.py +328 -0
- agentic_project_kit/release_metadata_prep.py +43 -0
- agentic_project_kit/release_notes.py +823 -0
- agentic_project_kit/release_prep_core.py +204 -0
- agentic_project_kit/release_prepare.py +369 -0
- agentic_project_kit/release_process_guardrails.py +247 -0
- agentic_project_kit/release_publish_core.py +114 -0
- agentic_project_kit/release_publish_orchestration.py +469 -0
- agentic_project_kit/release_state.py +552 -0
- agentic_project_kit/release_verify_core.py +175 -0
- agentic_project_kit/remote_branch_hygiene.py +223 -0
- agentic_project_kit/remote_next.py +89 -0
- agentic_project_kit/remote_next_closeout.py +101 -0
- agentic_project_kit/remote_next_order_contract.py +391 -0
- agentic_project_kit/removed_ns_commands.py +458 -0
- agentic_project_kit/removed_source_audit.py +225 -0
- agentic_project_kit/rendering.py +13 -0
- agentic_project_kit/repair_report.py +79 -0
- agentic_project_kit/repo_identity.py +59 -0
- agentic_project_kit/result_report_classifier.py +57 -0
- agentic_project_kit/rule_ack.py +105 -0
- agentic_project_kit/rule_preservation.py +58 -0
- agentic_project_kit/rule_refresh.py +174 -0
- agentic_project_kit/rule_registry_registration.py +322 -0
- agentic_project_kit/rule_registry_report.py +206 -0
- agentic_project_kit/rule_registry_validator.py +704 -0
- agentic_project_kit/rule_snapshot.py +98 -0
- agentic_project_kit/rule_source_validator.py +133 -0
- agentic_project_kit/run_summary.py +68 -0
- agentic_project_kit/run_summary_renderer.py +238 -0
- agentic_project_kit/runtime_validator.py +68 -0
- agentic_project_kit/safe_file_replace.py +35 -0
- agentic_project_kit/safe_patch.py +47 -0
- agentic_project_kit/safe_push.py +218 -0
- agentic_project_kit/safe_remove_diagnostic.py +51 -0
- agentic_project_kit/scaffold.py +69 -0
- agentic_project_kit/site_claims.py +444 -0
- agentic_project_kit/site_generator.py +1140 -0
- agentic_project_kit/slice_gate.py +225 -0
- agentic_project_kit/slice_runner.py +75 -0
- agentic_project_kit/standard_gates_audit_suite.py +167 -0
- agentic_project_kit/state_freshness.py +135 -0
- agentic_project_kit/status_current_state_audit.py +762 -0
- agentic_project_kit/successor_handoff_package.py +1763 -0
- agentic_project_kit/templates.py +292 -0
- agentic_project_kit/terminal_block_guard.py +166 -0
- agentic_project_kit/terminal_logging.py +275 -0
- agentic_project_kit/todo.py +97 -0
- agentic_project_kit/transfer_closeout.py +136 -0
- agentic_project_kit/transfer_continue.py +215 -0
- agentic_project_kit/transfer_local_runner.py +101 -0
- agentic_project_kit/transfer_operation_monitor.py +236 -0
- agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
- agentic_project_kit/transfer_post_merge_settle.py +383 -0
- agentic_project_kit/transfer_pr_actions.py +164 -0
- agentic_project_kit/transfer_remote_next.py +959 -0
- agentic_project_kit/transfer_repo_actions.py +2253 -0
- agentic_project_kit/transfer_runner.py +347 -0
- agentic_project_kit/transfer_safety_context.py +291 -0
- agentic_project_kit/transfer_state.py +483 -0
- agentic_project_kit/transfer_uplink.py +471 -0
- agentic_project_kit/transfer_workflow_next.py +148 -0
- agentic_project_kit/typed_work_order_evidence.py +58 -0
- agentic_project_kit/typed_work_order_queue.py +166 -0
- agentic_project_kit/typed_work_order_runner.py +168 -0
- agentic_project_kit/work_cycle.py +317 -0
- agentic_project_kit/work_discard_changes.py +129 -0
- agentic_project_kit/work_order_patch.py +199 -0
- agentic_project_kit/work_order_runner.py +120 -0
- agentic_project_kit/work_order_uploader.py +220 -0
- agentic_project_kit/work_order_validator.py +185 -0
- agentic_project_kit/work_orders.py +246 -0
- agentic_project_kit/workflow_guard.py +262 -0
- agentic_project_kit/workflow_summary_runner.py +124 -0
- agentic_project_kit/workspace.py +665 -0
- agentic_project_kit/workspace_adopt.py +471 -0
- agentic_project_kit/workspace_dpa_intake.py +406 -0
- agentic_project_kit/workspace_init.py +487 -0
- agentic_project_kit/workspace_lock.py +129 -0
- agentic_project_kit/workspace_remove.py +381 -0
- agentic_project_kit/workspace_upgrade.py +324 -0
- agentic_project_kit/wrapper_live_status.py +104 -0
- agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
- agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
- agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
- agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
- agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from agentic_project_kit.access_level_policy import visible_actions
|
|
8
|
+
from agentic_project_kit.access_levels import (
|
|
9
|
+
ACCESS_LEVEL_ORDER,
|
|
10
|
+
DEFAULT_ACCESS_LEVEL,
|
|
11
|
+
AccessLevel,
|
|
12
|
+
normalize_access_level,
|
|
13
|
+
)
|
|
14
|
+
from agentic_project_kit.action_registry import list_actions
|
|
15
|
+
from agentic_project_kit.cockpit import CockpitAction, cockpit_actions
|
|
16
|
+
from agentic_project_kit.gui_button_catalog import GuiButtonDefinition, basic_gui_buttons
|
|
17
|
+
from agentic_project_kit.gui_communication_modes import (
|
|
18
|
+
DEFAULT_COMMUNICATION_MODE,
|
|
19
|
+
communication_mode_definitions,
|
|
20
|
+
communication_mode_ids,
|
|
21
|
+
)
|
|
22
|
+
from agentic_project_kit.gui_gatekeeper_status import GuiGatekeeperStatus, build_gui_gatekeeper_status
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class GuiActionViewModel:
|
|
27
|
+
name: str
|
|
28
|
+
safety_class: str
|
|
29
|
+
description: str
|
|
30
|
+
enabled: bool
|
|
31
|
+
requires_confirmation: bool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class GuiControllerViewModel:
|
|
36
|
+
title: str
|
|
37
|
+
status: str
|
|
38
|
+
actions: tuple[GuiActionViewModel, ...]
|
|
39
|
+
destructive_actions_enabled: bool = False
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def action_count(self) -> int:
|
|
43
|
+
return len(self.actions)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class CommunicationModeViewModel:
|
|
48
|
+
mode_id: str
|
|
49
|
+
label: str
|
|
50
|
+
role: str
|
|
51
|
+
selected: bool
|
|
52
|
+
is_default: bool
|
|
53
|
+
safety_note: str
|
|
54
|
+
default_expanded_groups: tuple[str, ...] = ()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class BasicCockpitButtonViewModel:
|
|
59
|
+
command_id: str
|
|
60
|
+
label: str
|
|
61
|
+
tooltip: str
|
|
62
|
+
safety_class: str
|
|
63
|
+
enabled: bool
|
|
64
|
+
disabled_reason: str
|
|
65
|
+
wrapper_command: tuple[str, ...]
|
|
66
|
+
source: str
|
|
67
|
+
why: str
|
|
68
|
+
structured_explanation: str | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class BasicCockpitRecommendedAction:
|
|
73
|
+
label: str
|
|
74
|
+
description: str
|
|
75
|
+
tooltip: str
|
|
76
|
+
kind: str
|
|
77
|
+
command_id: str = ""
|
|
78
|
+
cockpit_action_id: str = ""
|
|
79
|
+
enabled: bool = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class BasicCockpitNextStepViewModel:
|
|
84
|
+
state_label: str
|
|
85
|
+
title: str
|
|
86
|
+
message: str
|
|
87
|
+
primary_label: str
|
|
88
|
+
primary_kind: str
|
|
89
|
+
primary_command_id: str
|
|
90
|
+
primary_cockpit_action_id: str
|
|
91
|
+
primary_enabled: bool
|
|
92
|
+
reason: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class BasicCockpitButtonGroupViewModel:
|
|
97
|
+
group_id: str
|
|
98
|
+
label: str
|
|
99
|
+
description: str
|
|
100
|
+
button_ids: tuple[str, ...]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class BasicCockpitViewModel:
|
|
105
|
+
title: str
|
|
106
|
+
traffic_light_state: str
|
|
107
|
+
traffic_light_color: str
|
|
108
|
+
reason: str
|
|
109
|
+
next_safe_action: str
|
|
110
|
+
evidence: str
|
|
111
|
+
mutation_allowed: bool
|
|
112
|
+
state_source: str
|
|
113
|
+
communication_mode: str
|
|
114
|
+
communication_context_fresh: bool
|
|
115
|
+
communication_context_reason: str
|
|
116
|
+
required_next_reply: str | None
|
|
117
|
+
access_level: str
|
|
118
|
+
access_level_options: tuple[str, ...]
|
|
119
|
+
access_level_explanation: str
|
|
120
|
+
communication_modes: tuple[CommunicationModeViewModel, ...]
|
|
121
|
+
buttons: tuple[BasicCockpitButtonViewModel, ...]
|
|
122
|
+
last_result: str
|
|
123
|
+
explanation: str
|
|
124
|
+
recommended_action: BasicCockpitRecommendedAction = field(
|
|
125
|
+
default_factory=lambda: BasicCockpitRecommendedAction(
|
|
126
|
+
label="Inspect state",
|
|
127
|
+
description="Refresh status before choosing a next action.",
|
|
128
|
+
tooltip="Refresh the GUI gatekeeper status before any mutation.",
|
|
129
|
+
kind="run_button",
|
|
130
|
+
command_id="status-refresh",
|
|
131
|
+
enabled=True,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
next_step: BasicCockpitNextStepViewModel = field(
|
|
135
|
+
default_factory=lambda: BasicCockpitNextStepViewModel(
|
|
136
|
+
state_label="READY",
|
|
137
|
+
title="Next step",
|
|
138
|
+
message="Refresh status before choosing a next action.",
|
|
139
|
+
primary_label="Refresh status",
|
|
140
|
+
primary_kind="run_button",
|
|
141
|
+
primary_command_id="status-refresh",
|
|
142
|
+
primary_cockpit_action_id="workflow.state",
|
|
143
|
+
primary_enabled=True,
|
|
144
|
+
reason="State has not been inspected yet.",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
button_groups: tuple[BasicCockpitButtonGroupViewModel, ...] = ()
|
|
148
|
+
recovery_hint: str = ""
|
|
149
|
+
last_transfer_message: str = "No transfer task has been sent in this GUI session."
|
|
150
|
+
default_expanded_groups: tuple[str, ...] = ()
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def button_count(self) -> int:
|
|
154
|
+
return len(self.buttons)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _value(value: object, default: str = "") -> str:
|
|
158
|
+
if value is None:
|
|
159
|
+
return default
|
|
160
|
+
raw = getattr(value, "value", value)
|
|
161
|
+
return str(raw)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _field(action: object, *names: str, default: object = "") -> object:
|
|
165
|
+
for name in names:
|
|
166
|
+
if hasattr(action, name):
|
|
167
|
+
return getattr(action, name)
|
|
168
|
+
if isinstance(action, dict) and name in action:
|
|
169
|
+
return action[name]
|
|
170
|
+
return default
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _normalize_state(value: object) -> str:
|
|
174
|
+
return str(value or "").strip().upper().replace("-", "_")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _traffic_light_from_gatekeeper_status(status: GuiGatekeeperStatus) -> tuple[str, str, str, str]:
|
|
178
|
+
workflow_state = _normalize_state(status.workflow_state)
|
|
179
|
+
current_work_state = _normalize_state(status.current_work_state)
|
|
180
|
+
combined = {workflow_state, current_work_state}
|
|
181
|
+
if status.required_next_reply == "d2" and not status.communication_context_fresh:
|
|
182
|
+
return (
|
|
183
|
+
"WAIT_FOR_D2",
|
|
184
|
+
"yellow",
|
|
185
|
+
status.communication_context_reason,
|
|
186
|
+
"Send d2 only after the remote rule capsule is readable, then validate RULE_REFRESH_ACK.",
|
|
187
|
+
)
|
|
188
|
+
if combined & {"FAILED", "FAIL", "ERROR"}:
|
|
189
|
+
return (
|
|
190
|
+
"FAILED",
|
|
191
|
+
"red",
|
|
192
|
+
"The last known workflow state is failed.",
|
|
193
|
+
"Open diagnostics and preserve evidence before any mutation.",
|
|
194
|
+
)
|
|
195
|
+
if status.blockers:
|
|
196
|
+
return (
|
|
197
|
+
"BLOCKED",
|
|
198
|
+
"red",
|
|
199
|
+
"; ".join(status.blockers),
|
|
200
|
+
"Resolve deterministic blockers before running mutating actions.",
|
|
201
|
+
)
|
|
202
|
+
if combined & {"WAIT", "WAITING", "PENDING", "RUNNING", "REQUESTED", "UPLOADED"}:
|
|
203
|
+
return (
|
|
204
|
+
"WAIT",
|
|
205
|
+
"yellow",
|
|
206
|
+
f"Waiting workflow state: {status.current_work_state or status.workflow_state}.",
|
|
207
|
+
"Wait for CI, remote state, transfer files, or wrapper completion.",
|
|
208
|
+
)
|
|
209
|
+
return (
|
|
210
|
+
"READY",
|
|
211
|
+
"green",
|
|
212
|
+
"Gatekeeper state allows the next safe action.",
|
|
213
|
+
"Use File Transfer as the default path or run a read-only diagnostic.",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _communication_modes(selected_mode: str) -> tuple[CommunicationModeViewModel, ...]:
|
|
218
|
+
selected = selected_mode if selected_mode in communication_mode_ids() else DEFAULT_COMMUNICATION_MODE
|
|
219
|
+
return tuple(
|
|
220
|
+
CommunicationModeViewModel(
|
|
221
|
+
definition.mode_id,
|
|
222
|
+
definition.label,
|
|
223
|
+
definition.role,
|
|
224
|
+
selected == definition.mode_id,
|
|
225
|
+
definition.is_default,
|
|
226
|
+
definition.safety_note,
|
|
227
|
+
definition.default_expanded_groups,
|
|
228
|
+
)
|
|
229
|
+
for definition in communication_mode_definitions()
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def access_level_explanation(access_level: str) -> str:
|
|
234
|
+
selected = normalize_access_level(access_level)
|
|
235
|
+
if selected == "basic":
|
|
236
|
+
return "Basic shows status, routine work-order actions, and safe dirty-state recovery."
|
|
237
|
+
if selected == "advanced":
|
|
238
|
+
return "Advanced adds release, rules, and handoff workflow actions."
|
|
239
|
+
return "Maintainer adds deep governance and drift audits."
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def cockpit_actions_for_access_level(
|
|
243
|
+
actions: Iterable[CockpitAction] | None = None,
|
|
244
|
+
*,
|
|
245
|
+
access_level: str = DEFAULT_ACCESS_LEVEL,
|
|
246
|
+
) -> tuple[CockpitAction, ...]:
|
|
247
|
+
selected_level = normalize_access_level(access_level)
|
|
248
|
+
selected_actions = list(actions) if actions is not None else cockpit_actions()
|
|
249
|
+
return tuple(visible_actions(selected_actions, selected_level))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _button_is_mutating(button: GuiButtonDefinition) -> bool:
|
|
253
|
+
return button.safety_class in {
|
|
254
|
+
"bounded-local-mutation",
|
|
255
|
+
"bounded-mutation",
|
|
256
|
+
"local-only",
|
|
257
|
+
"remote-mutation",
|
|
258
|
+
"destructive",
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _restore_volatile_allowed(status: GuiGatekeeperStatus, traffic_state: str) -> bool:
|
|
263
|
+
current_work_state = _normalize_state(status.current_work_state)
|
|
264
|
+
workflow_state = _normalize_state(status.workflow_state)
|
|
265
|
+
if current_work_state in {"FAILED", "FAIL", "ERROR"} or workflow_state in {"FAILED", "FAIL", "ERROR"}:
|
|
266
|
+
return False
|
|
267
|
+
if status.required_next_reply == "d2" and not status.communication_context_fresh:
|
|
268
|
+
return False
|
|
269
|
+
return traffic_state == "READY" or (
|
|
270
|
+
status.git_dirty
|
|
271
|
+
and status.communication_context_fresh
|
|
272
|
+
and workflow_state in {"", "IDLE", "READY", "MISSING"}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _button_enabled_for_basic(button: GuiButtonDefinition, status: GuiGatekeeperStatus, traffic_state: str) -> tuple[bool, str, str]:
|
|
277
|
+
if button.command_id == "restore-volatile":
|
|
278
|
+
if not button.enabled:
|
|
279
|
+
return (
|
|
280
|
+
False,
|
|
281
|
+
button.disabled_reason or "known volatile restore is disabled by policy",
|
|
282
|
+
"Restore Volatile is the only dirty-state recovery button and remains wrapper-backed.",
|
|
283
|
+
)
|
|
284
|
+
if _restore_volatile_allowed(status, traffic_state):
|
|
285
|
+
return (
|
|
286
|
+
True,
|
|
287
|
+
"",
|
|
288
|
+
"known volatile restore is allowed as bounded recovery; product changes remain blocked",
|
|
289
|
+
)
|
|
290
|
+
return (
|
|
291
|
+
False,
|
|
292
|
+
"restore volatile is blocked while failed workflow evidence or d2 acknowledgement is pending",
|
|
293
|
+
"Preserve failed workflow evidence and communication-rule state before cleanup.",
|
|
294
|
+
)
|
|
295
|
+
if button.safety_class in {"remote-mutation", "destructive"}:
|
|
296
|
+
return False, "remote/destructive actions are blocked in Basic Mode", "Basic Mode exposes readiness only for this action class."
|
|
297
|
+
if "release" in button.command_id or "publish" in button.command_id:
|
|
298
|
+
return False, "release publish/destructive actions are blocked in Basic Mode", "Release controls stay readiness-only until a guarded maintainer flow exists."
|
|
299
|
+
if not _button_is_mutating(button):
|
|
300
|
+
return True, "", "read-only diagnostic or status action is allowed"
|
|
301
|
+
if not button.enabled:
|
|
302
|
+
return (
|
|
303
|
+
False,
|
|
304
|
+
button.disabled_reason or "mutating Basic action has no guarded dispatcher yet",
|
|
305
|
+
"Basic Mode may show this capability, but execution stays disabled until the catalog marks it safe.",
|
|
306
|
+
)
|
|
307
|
+
if traffic_state == "READY" and status.ready_for_mutating_actions:
|
|
308
|
+
return True, "", "mutating local/fixed-path action is allowed only after gatekeeper revalidation"
|
|
309
|
+
reason = "mutating action requires READY state and clean gatekeeper revalidation"
|
|
310
|
+
if status.git_dirty:
|
|
311
|
+
reason = "mutating action blocked because working tree is dirty"
|
|
312
|
+
elif traffic_state != "READY":
|
|
313
|
+
reason = f"mutating action blocked while traffic light is {traffic_state}"
|
|
314
|
+
return False, reason, "Basic Mode keeps mutation disabled until deterministic state is READY."
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _basic_button_view_models(
|
|
318
|
+
status: GuiGatekeeperStatus,
|
|
319
|
+
*,
|
|
320
|
+
traffic_state: str,
|
|
321
|
+
buttons: Iterable[GuiButtonDefinition] | None = None,
|
|
322
|
+
) -> tuple[BasicCockpitButtonViewModel, ...]:
|
|
323
|
+
selected = tuple(buttons) if buttons is not None else basic_gui_buttons()
|
|
324
|
+
items: list[BasicCockpitButtonViewModel] = []
|
|
325
|
+
for button in selected:
|
|
326
|
+
enabled, disabled_reason, why = _button_enabled_for_basic(button, status, traffic_state)
|
|
327
|
+
if not disabled_reason and not button.enabled and not _button_is_mutating(button):
|
|
328
|
+
disabled_reason = button.disabled_reason
|
|
329
|
+
enabled = False
|
|
330
|
+
items.append(
|
|
331
|
+
BasicCockpitButtonViewModel(
|
|
332
|
+
command_id=button.command_id,
|
|
333
|
+
label=button.label,
|
|
334
|
+
tooltip=button.tooltip,
|
|
335
|
+
safety_class=button.safety_class,
|
|
336
|
+
enabled=enabled,
|
|
337
|
+
disabled_reason=disabled_reason,
|
|
338
|
+
wrapper_command=button.wrapper_command,
|
|
339
|
+
source="gui_button_catalog",
|
|
340
|
+
why=why,
|
|
341
|
+
structured_explanation=button.structured_explanation,
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
return tuple(items)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _recommended_action_for_state(
|
|
348
|
+
status: GuiGatekeeperStatus,
|
|
349
|
+
*,
|
|
350
|
+
traffic_state: str,
|
|
351
|
+
next_action: str,
|
|
352
|
+
mutation_allowed: bool,
|
|
353
|
+
) -> BasicCockpitRecommendedAction:
|
|
354
|
+
if status.required_next_reply == "d2" and not status.communication_context_fresh:
|
|
355
|
+
return BasicCockpitRecommendedAction(
|
|
356
|
+
label="Complete d2 acknowledgement",
|
|
357
|
+
description=next_action,
|
|
358
|
+
tooltip="Send d2 in chat and wait for RULE_REFRESH_ACK before any mutation.",
|
|
359
|
+
kind="show_hint",
|
|
360
|
+
enabled=False,
|
|
361
|
+
)
|
|
362
|
+
if traffic_state in {"FAILED", "BLOCKED"}:
|
|
363
|
+
return BasicCockpitRecommendedAction(
|
|
364
|
+
label="Open diagnostics",
|
|
365
|
+
description="Load Doctor so the blocker can be inspected without running it or mutating the repository.",
|
|
366
|
+
tooltip="Select the read-only Doctor action. It does not repair or mutate by itself.",
|
|
367
|
+
kind="select_action",
|
|
368
|
+
cockpit_action_id="gate.doctor",
|
|
369
|
+
enabled=True,
|
|
370
|
+
)
|
|
371
|
+
if traffic_state == "WAIT":
|
|
372
|
+
return BasicCockpitRecommendedAction(
|
|
373
|
+
label="Show state and next step",
|
|
374
|
+
description=next_action,
|
|
375
|
+
tooltip="Select the read-only workflow state action while the workflow is waiting.",
|
|
376
|
+
kind="select_action",
|
|
377
|
+
cockpit_action_id="workflow.state",
|
|
378
|
+
enabled=True,
|
|
379
|
+
)
|
|
380
|
+
if mutation_allowed:
|
|
381
|
+
return BasicCockpitRecommendedAction(
|
|
382
|
+
label="Run next work order",
|
|
383
|
+
description=next_action,
|
|
384
|
+
tooltip="Run the registered Basic action for the next guarded file-transfer work order.",
|
|
385
|
+
kind="run_button",
|
|
386
|
+
command_id="run-next-work-order",
|
|
387
|
+
cockpit_action_id="dialog.rn",
|
|
388
|
+
enabled=True,
|
|
389
|
+
)
|
|
390
|
+
return BasicCockpitRecommendedAction(
|
|
391
|
+
label="Refresh status",
|
|
392
|
+
description=next_action,
|
|
393
|
+
tooltip="Refresh gatekeeper state before choosing a mutating action.",
|
|
394
|
+
kind="run_button",
|
|
395
|
+
command_id="status-refresh",
|
|
396
|
+
cockpit_action_id="workflow.state",
|
|
397
|
+
enabled=True,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _next_step_view(
|
|
402
|
+
*,
|
|
403
|
+
traffic_state: str,
|
|
404
|
+
reason: str,
|
|
405
|
+
recommended_action: BasicCockpitRecommendedAction,
|
|
406
|
+
) -> BasicCockpitNextStepViewModel:
|
|
407
|
+
title_by_state = {
|
|
408
|
+
"READY": "Ready for the next safe step",
|
|
409
|
+
"WAIT_FOR_D2": "Communication refresh required",
|
|
410
|
+
"WAIT": "Waiting for external state",
|
|
411
|
+
"BLOCKED": "Fix blockers before continuing",
|
|
412
|
+
"FAILED": "Preserve evidence and diagnose",
|
|
413
|
+
}
|
|
414
|
+
return BasicCockpitNextStepViewModel(
|
|
415
|
+
state_label=traffic_state,
|
|
416
|
+
title=title_by_state.get(traffic_state, "Next step"),
|
|
417
|
+
message=recommended_action.description,
|
|
418
|
+
primary_label=recommended_action.label,
|
|
419
|
+
primary_kind=recommended_action.kind,
|
|
420
|
+
primary_command_id=recommended_action.command_id,
|
|
421
|
+
primary_cockpit_action_id=recommended_action.cockpit_action_id,
|
|
422
|
+
primary_enabled=recommended_action.enabled,
|
|
423
|
+
reason=reason,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _button_group_for(button: BasicCockpitButtonViewModel) -> tuple[str, str, str]:
|
|
428
|
+
command_id = button.command_id
|
|
429
|
+
if command_id in {"status-refresh", "run-next-work-order", "close-out-last-run"}:
|
|
430
|
+
return ("routine", "Routine", "Normal next-step and closeout controls.")
|
|
431
|
+
if command_id in {"communication-rules-refresh", "handoff-rules-refresh"}:
|
|
432
|
+
return ("transfer", "Transfer", "Communication, transfer, and handoff controls.")
|
|
433
|
+
if command_id in {"diagnose", "doctor", "check-docs"} or "diagnose" in command_id:
|
|
434
|
+
return ("diagnostics", "Diagnostics", "Read-only checks for blockers and drift.")
|
|
435
|
+
return ("advanced", "Advanced", "Less common controls that stay behind policy gates.")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _group_basic_buttons(
|
|
439
|
+
buttons: tuple[BasicCockpitButtonViewModel, ...],
|
|
440
|
+
) -> tuple[BasicCockpitButtonGroupViewModel, ...]:
|
|
441
|
+
order = ("routine", "transfer", "diagnostics", "advanced")
|
|
442
|
+
metadata: dict[str, tuple[str, str]] = {}
|
|
443
|
+
grouped: dict[str, list[str]] = {}
|
|
444
|
+
for button in buttons:
|
|
445
|
+
group_id, label, description = _button_group_for(button)
|
|
446
|
+
metadata[group_id] = (label, description)
|
|
447
|
+
grouped.setdefault(group_id, []).append(button.command_id)
|
|
448
|
+
result: list[BasicCockpitButtonGroupViewModel] = []
|
|
449
|
+
for group_id in order:
|
|
450
|
+
button_ids = tuple(grouped.get(group_id, ()))
|
|
451
|
+
if not button_ids:
|
|
452
|
+
continue
|
|
453
|
+
label, description = metadata[group_id]
|
|
454
|
+
result.append(
|
|
455
|
+
BasicCockpitButtonGroupViewModel(
|
|
456
|
+
group_id=group_id,
|
|
457
|
+
label=label,
|
|
458
|
+
description=description,
|
|
459
|
+
button_ids=button_ids,
|
|
460
|
+
)
|
|
461
|
+
)
|
|
462
|
+
return tuple(result)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _recovery_hint(status: GuiGatekeeperStatus, traffic_state: str) -> str:
|
|
466
|
+
if status.required_next_reply == "d2" and not status.communication_context_fresh:
|
|
467
|
+
return "Recovery: send d2 in chat and require RULE_REFRESH_ACK before running or sending tasks."
|
|
468
|
+
if traffic_state == "FAILED":
|
|
469
|
+
return "Recovery: preserve evidence, inspect diagnostics, and do not clean up until the failure is classified."
|
|
470
|
+
if traffic_state == "BLOCKED":
|
|
471
|
+
return "Recovery: resolve deterministic blockers first; read-only diagnostics remain safe."
|
|
472
|
+
if traffic_state == "WAIT":
|
|
473
|
+
return "Recovery: wait for the external workflow, CI, or transfer state to settle before retrying."
|
|
474
|
+
return ""
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def build_basic_cockpit_view_model(
|
|
478
|
+
project_root: Path | str = ".",
|
|
479
|
+
*,
|
|
480
|
+
gatekeeper_status: GuiGatekeeperStatus | None = None,
|
|
481
|
+
communication_mode: str = "file_transfer",
|
|
482
|
+
access_level: str = DEFAULT_ACCESS_LEVEL,
|
|
483
|
+
buttons: Iterable[GuiButtonDefinition] | None = None,
|
|
484
|
+
) -> BasicCockpitViewModel:
|
|
485
|
+
status = gatekeeper_status or build_gui_gatekeeper_status(project_root)
|
|
486
|
+
traffic_state, color, reason, next_action = _traffic_light_from_gatekeeper_status(status)
|
|
487
|
+
modes = _communication_modes(communication_mode)
|
|
488
|
+
selected_mode = next((mode.mode_id for mode in modes if mode.selected), "file_transfer")
|
|
489
|
+
default_expanded_groups = next(
|
|
490
|
+
(mode.default_expanded_groups for mode in modes if mode.selected),
|
|
491
|
+
(),
|
|
492
|
+
)
|
|
493
|
+
selected_access_level: AccessLevel = normalize_access_level(access_level)
|
|
494
|
+
mutation_allowed = traffic_state == "READY" and status.ready_for_mutating_actions
|
|
495
|
+
button_models = _basic_button_view_models(status, traffic_state=traffic_state, buttons=buttons)
|
|
496
|
+
recommended_action = _recommended_action_for_state(
|
|
497
|
+
status,
|
|
498
|
+
traffic_state=traffic_state,
|
|
499
|
+
next_action=next_action,
|
|
500
|
+
mutation_allowed=mutation_allowed,
|
|
501
|
+
)
|
|
502
|
+
evidence = (
|
|
503
|
+
f"branch={status.branch}; workflow_state={status.workflow_state}; "
|
|
504
|
+
f"current_work_state={status.current_work_state or '<none>'}"
|
|
505
|
+
)
|
|
506
|
+
return BasicCockpitViewModel(
|
|
507
|
+
title="agentic-project-kit Cockpit - Basic",
|
|
508
|
+
traffic_light_state=traffic_state,
|
|
509
|
+
traffic_light_color=color,
|
|
510
|
+
reason=reason,
|
|
511
|
+
next_safe_action=next_action,
|
|
512
|
+
evidence=evidence,
|
|
513
|
+
mutation_allowed=mutation_allowed,
|
|
514
|
+
state_source="gui_gatekeeper_status",
|
|
515
|
+
communication_mode=selected_mode,
|
|
516
|
+
communication_context_fresh=status.communication_context_fresh,
|
|
517
|
+
communication_context_reason=status.communication_context_reason,
|
|
518
|
+
required_next_reply=status.required_next_reply,
|
|
519
|
+
access_level=selected_access_level,
|
|
520
|
+
access_level_options=tuple(ACCESS_LEVEL_ORDER),
|
|
521
|
+
access_level_explanation=access_level_explanation(selected_access_level),
|
|
522
|
+
communication_modes=modes,
|
|
523
|
+
buttons=button_models,
|
|
524
|
+
last_result="No action has run in this GUI session.",
|
|
525
|
+
explanation="Basic Mode is a thin control surface over registered wrappers, gatekeeper state, typed work orders, evidence, and readiness reports.",
|
|
526
|
+
recommended_action=recommended_action,
|
|
527
|
+
next_step=_next_step_view(
|
|
528
|
+
traffic_state=traffic_state,
|
|
529
|
+
reason=reason,
|
|
530
|
+
recommended_action=recommended_action,
|
|
531
|
+
),
|
|
532
|
+
button_groups=_group_basic_buttons(button_models),
|
|
533
|
+
recovery_hint=_recovery_hint(status, traffic_state),
|
|
534
|
+
default_expanded_groups=default_expanded_groups,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def action_to_view_model(action: object, *, destructive_actions_enabled: bool = False) -> GuiActionViewModel:
|
|
539
|
+
name = _value(_field(action, "name", "action_id", "id"))
|
|
540
|
+
safety_class = _value(_field(action, "safety_class", "safety", default="unknown"))
|
|
541
|
+
description = _value(_field(action, "description", "summary", default=""))
|
|
542
|
+
normalized_safety_class = safety_class.lower().replace("_", "-")
|
|
543
|
+
destructive = normalized_safety_class in {"destructive", "release", "remote", "mutation", "remote-mutation"}
|
|
544
|
+
return GuiActionViewModel(
|
|
545
|
+
name=name,
|
|
546
|
+
safety_class=safety_class,
|
|
547
|
+
description=description,
|
|
548
|
+
enabled=not destructive or destructive_actions_enabled,
|
|
549
|
+
requires_confirmation=destructive,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def build_gui_controller_view_model(
|
|
554
|
+
actions: Iterable[object],
|
|
555
|
+
*,
|
|
556
|
+
title: str = "agentic-project-kit Cockpit",
|
|
557
|
+
status: str = "ready",
|
|
558
|
+
destructive_actions_enabled: bool = False,
|
|
559
|
+
) -> GuiControllerViewModel:
|
|
560
|
+
view_actions = tuple(
|
|
561
|
+
action_to_view_model(action, destructive_actions_enabled=destructive_actions_enabled)
|
|
562
|
+
for action in actions
|
|
563
|
+
)
|
|
564
|
+
return GuiControllerViewModel(
|
|
565
|
+
title=title,
|
|
566
|
+
status=status,
|
|
567
|
+
actions=view_actions,
|
|
568
|
+
destructive_actions_enabled=destructive_actions_enabled,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@dataclass(frozen=True)
|
|
574
|
+
class GuiActionDetail:
|
|
575
|
+
name: str
|
|
576
|
+
label: str
|
|
577
|
+
description: str
|
|
578
|
+
safety_class: str
|
|
579
|
+
enabled: bool
|
|
580
|
+
requires_confirmation: bool
|
|
581
|
+
disabled_reason: str
|
|
582
|
+
tooltip: str
|
|
583
|
+
icon_id: str
|
|
584
|
+
evidence_hint: str
|
|
585
|
+
parameter_hint: str
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@dataclass(frozen=True)
|
|
589
|
+
class GuiActionDetailPanel:
|
|
590
|
+
title: str
|
|
591
|
+
selected_action_name: str
|
|
592
|
+
actions: tuple[GuiActionDetail, ...]
|
|
593
|
+
disabled_action_names: tuple[str, ...]
|
|
594
|
+
status_message: str
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _gui_detail_value(value: object) -> str:
|
|
598
|
+
enum_value = getattr(value, "value", None)
|
|
599
|
+
if enum_value is not None:
|
|
600
|
+
return str(enum_value)
|
|
601
|
+
return str(value)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _gui_detail_label(name: str) -> str:
|
|
605
|
+
return name.replace("-", " ").title()
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _gui_detail_confirmation_required(safety_class: str, enabled: bool) -> bool:
|
|
609
|
+
lowered = safety_class.lower()
|
|
610
|
+
return enabled and any(token in lowered for token in ("remote", "mutation", "destructive", "release"))
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _gui_detail_evidence_hint(safety_class: str, enabled: bool) -> str:
|
|
614
|
+
lowered = safety_class.lower()
|
|
615
|
+
if not enabled:
|
|
616
|
+
return "disabled action; no execution evidence expected"
|
|
617
|
+
if "read" in lowered:
|
|
618
|
+
return "local transcript is sufficient unless promoted to a workflow evidence slice"
|
|
619
|
+
if "local" in lowered:
|
|
620
|
+
return "local terminal log is required; remote evidence only after commit and push"
|
|
621
|
+
return "remote committed evidence is required before claiming no-copy completion"
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _gui_detail_parameter_hint(action: object | None) -> str:
|
|
625
|
+
parameters = getattr(action, "parameters", None)
|
|
626
|
+
if parameters:
|
|
627
|
+
return ", ".join(str(parameter) for parameter in parameters)
|
|
628
|
+
return "no structured parameters registered"
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _gui_detail_button_metadata() -> dict[str, tuple[str, str]]:
|
|
632
|
+
result: dict[str, tuple[str, str]] = {}
|
|
633
|
+
for action in list_actions():
|
|
634
|
+
name = str(getattr(action, "name", ""))
|
|
635
|
+
if name:
|
|
636
|
+
result[name] = (f"Run {name} through the governed GUI action layer.", name.replace("-", "_"))
|
|
637
|
+
result["release-publish"] = ("Publishing is disabled in the GUI until explicit release confirmation is implemented.", "release_publish_disabled")
|
|
638
|
+
return result
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def build_gui_action_detail_panel(selected_action_name: str = "") -> GuiActionDetailPanel:
|
|
642
|
+
disabled = {"release-publish"}
|
|
643
|
+
button_metadata = _gui_detail_button_metadata()
|
|
644
|
+
items: list[GuiActionDetail] = []
|
|
645
|
+
for action in list_actions():
|
|
646
|
+
name = str(getattr(action, "name", ""))
|
|
647
|
+
if not name:
|
|
648
|
+
continue
|
|
649
|
+
tooltip, icon_id = button_metadata.get(name, ("No toolbar/action button assigned yet.", "none"))
|
|
650
|
+
enabled = name not in disabled
|
|
651
|
+
safety_class = _gui_detail_value(getattr(action, "safety_class", "unknown"))
|
|
652
|
+
description = str(getattr(action, "description", ""))
|
|
653
|
+
disabled_reason = "disabled by GUI safety policy" if not enabled else ""
|
|
654
|
+
items.append(
|
|
655
|
+
GuiActionDetail(
|
|
656
|
+
name=name,
|
|
657
|
+
label=_gui_detail_label(name),
|
|
658
|
+
description=description,
|
|
659
|
+
safety_class=safety_class,
|
|
660
|
+
enabled=enabled,
|
|
661
|
+
requires_confirmation=_gui_detail_confirmation_required(safety_class, enabled),
|
|
662
|
+
disabled_reason=disabled_reason,
|
|
663
|
+
tooltip=tooltip,
|
|
664
|
+
icon_id=icon_id,
|
|
665
|
+
evidence_hint=_gui_detail_evidence_hint(safety_class, enabled),
|
|
666
|
+
parameter_hint=_gui_detail_parameter_hint(action),
|
|
667
|
+
)
|
|
668
|
+
)
|
|
669
|
+
existing_names = {item.name for item in items}
|
|
670
|
+
if "release-publish" not in existing_names:
|
|
671
|
+
tooltip, icon_id = button_metadata.get(
|
|
672
|
+
"release-publish",
|
|
673
|
+
("Publishing is disabled in the GUI until explicit release confirmation is implemented.", "release_publish_disabled"),
|
|
674
|
+
)
|
|
675
|
+
items.append(
|
|
676
|
+
GuiActionDetail(
|
|
677
|
+
name="release-publish",
|
|
678
|
+
label=_gui_detail_label("release-publish"),
|
|
679
|
+
description="Publishes a release; disabled in the GUI until explicit confirmation support exists.",
|
|
680
|
+
safety_class="destructive",
|
|
681
|
+
enabled=False,
|
|
682
|
+
requires_confirmation=False,
|
|
683
|
+
disabled_reason="disabled by GUI safety policy",
|
|
684
|
+
tooltip=tooltip,
|
|
685
|
+
icon_id=icon_id,
|
|
686
|
+
evidence_hint="disabled action; no execution evidence expected",
|
|
687
|
+
parameter_hint="disabled GUI-only release action",
|
|
688
|
+
)
|
|
689
|
+
)
|
|
690
|
+
items_tuple = tuple(sorted(items, key=lambda item: item.name))
|
|
691
|
+
selected = selected_action_name if any(item.name == selected_action_name for item in items_tuple) else ""
|
|
692
|
+
return GuiActionDetailPanel(
|
|
693
|
+
title="Action details",
|
|
694
|
+
selected_action_name=selected,
|
|
695
|
+
actions=items_tuple,
|
|
696
|
+
disabled_action_names=tuple(sorted(disabled)),
|
|
697
|
+
status_message="ready" if items_tuple else "no actions registered",
|
|
698
|
+
)
|