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,748 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agentic_project_kit.gui_activity_log import ActivityEntry
|
|
9
|
+
from agentic_project_kit.gui_cockpit_common import THEME
|
|
10
|
+
from agentic_project_kit.gui_open_folder import open_folder_in_file_manager
|
|
11
|
+
from agentic_project_kit.gui_task_editor import (
|
|
12
|
+
CANONICAL_TRANSFER_INBOX_PATH,
|
|
13
|
+
CANONICAL_TRANSFER_OUTBOX_PATH,
|
|
14
|
+
TaskEditorState,
|
|
15
|
+
open_transfer_terminal as open_transfer_terminal_for_project,
|
|
16
|
+
standard_command_args_for_communication_mode,
|
|
17
|
+
standard_command_description_for_communication_mode,
|
|
18
|
+
standard_command_label_for_communication_mode,
|
|
19
|
+
task_editor_send_enabled,
|
|
20
|
+
task_editor_state_after_read,
|
|
21
|
+
task_editor_state_after_send,
|
|
22
|
+
task_editor_visible_for_mode,
|
|
23
|
+
transfer_state_has_canonical_outbox_result,
|
|
24
|
+
)
|
|
25
|
+
from agentic_project_kit.gui_tk_widgets import attach_tooltip
|
|
26
|
+
from agentic_project_kit.workspace import load_workspace
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CockpitTaskMixin:
|
|
30
|
+
def _build_task_editor(self, parent: Any) -> None:
|
|
31
|
+
import tkinter as tk
|
|
32
|
+
from tkinter import ttk
|
|
33
|
+
|
|
34
|
+
self.task_editor_state = TaskEditorState.IDLE
|
|
35
|
+
self.task_status_var = tk.StringVar(value="Write a GUI task, then send it through the guarded wrapper.")
|
|
36
|
+
if not task_editor_visible_for_mode(self.basic_view.communication_mode):
|
|
37
|
+
self.task_text = None
|
|
38
|
+
self.initial_prompt_button = None
|
|
39
|
+
self.task_send_button = None
|
|
40
|
+
self.task_read_button = None
|
|
41
|
+
self.task_open_terminal_button = None
|
|
42
|
+
self.task_standard_command_button = None
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
group = self._build_collapsible_group(
|
|
46
|
+
parent,
|
|
47
|
+
group_id="task_editor",
|
|
48
|
+
title="File transfer task",
|
|
49
|
+
subtitle="writes to repo · publishes",
|
|
50
|
+
)
|
|
51
|
+
task_frame = group.body
|
|
52
|
+
self.task_open_terminal_button = None
|
|
53
|
+
self.task_standard_command_button = None
|
|
54
|
+
heading_row = tk.Frame(task_frame, bg=THEME.color_panel_bg)
|
|
55
|
+
heading_row.pack(fill=tk.X)
|
|
56
|
+
tk.Label(
|
|
57
|
+
heading_row,
|
|
58
|
+
text="Describe the task for the LLM.",
|
|
59
|
+
bg=THEME.color_panel_bg,
|
|
60
|
+
font=THEME.body_font,
|
|
61
|
+
anchor=tk.W,
|
|
62
|
+
).pack(side=tk.LEFT)
|
|
63
|
+
tk.Label(
|
|
64
|
+
heading_row,
|
|
65
|
+
text="writes to repo · publishes",
|
|
66
|
+
bg=THEME.color_panel_bg,
|
|
67
|
+
fg=THEME.color_muted_text,
|
|
68
|
+
font=THEME.small_font,
|
|
69
|
+
).pack(side=tk.RIGHT)
|
|
70
|
+
|
|
71
|
+
self.task_text = tk.Text(
|
|
72
|
+
task_frame,
|
|
73
|
+
height=THEME.task_text_height,
|
|
74
|
+
wrap=tk.WORD,
|
|
75
|
+
font=THEME.body_font,
|
|
76
|
+
relief=tk.GROOVE,
|
|
77
|
+
bd=1,
|
|
78
|
+
padx=9,
|
|
79
|
+
pady=7,
|
|
80
|
+
)
|
|
81
|
+
self.task_text.pack(fill=tk.X, expand=False, pady=(9, 9))
|
|
82
|
+
self.task_text.bind("<KeyRelease>", self.refresh_task_editor_buttons)
|
|
83
|
+
task_button_row = tk.Frame(task_frame, bg=THEME.color_panel_bg)
|
|
84
|
+
task_button_row.pack(fill=tk.X)
|
|
85
|
+
self.initial_prompt_button = ttk.Button(
|
|
86
|
+
task_button_row,
|
|
87
|
+
text="Initial prompt",
|
|
88
|
+
command=self.show_initial_llm_prompt,
|
|
89
|
+
)
|
|
90
|
+
attach_tooltip(
|
|
91
|
+
self.initial_prompt_button,
|
|
92
|
+
"Render the one-time initial LLM prompt for file-transfer dialog setup.",
|
|
93
|
+
)
|
|
94
|
+
self.initial_prompt_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
95
|
+
self.task_send_button = ttk.Button(task_button_row, text="Send", command=self.send_user_task)
|
|
96
|
+
attach_tooltip(
|
|
97
|
+
self.task_send_button,
|
|
98
|
+
"Publish the canonical agentic-kit transfer inbox file "
|
|
99
|
+
f"{CANONICAL_TRANSFER_INBOX_PATH.as_posix()} through "
|
|
100
|
+
"agentic-kit transfer submit-user-task --publish.",
|
|
101
|
+
)
|
|
102
|
+
self.task_send_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
103
|
+
self.task_read_button = ttk.Button(
|
|
104
|
+
task_button_row,
|
|
105
|
+
text="Read",
|
|
106
|
+
command=self.read_last_task_result,
|
|
107
|
+
state=tk.DISABLED,
|
|
108
|
+
)
|
|
109
|
+
attach_tooltip(
|
|
110
|
+
self.task_read_button,
|
|
111
|
+
"Read canonical transfer state through agentic-kit transfer state --json; the local result is .agentic/transfer/outbox/last_result.txt.",
|
|
112
|
+
)
|
|
113
|
+
self.task_read_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
114
|
+
tk.Label(
|
|
115
|
+
task_frame,
|
|
116
|
+
textvariable=self.task_status_var,
|
|
117
|
+
anchor=tk.W,
|
|
118
|
+
justify=tk.LEFT,
|
|
119
|
+
bg=THEME.color_panel_bg,
|
|
120
|
+
fg=THEME.color_muted_text,
|
|
121
|
+
font=THEME.small_font,
|
|
122
|
+
wraplength=620,
|
|
123
|
+
).pack(fill=tk.X, pady=(7, 0))
|
|
124
|
+
self.refresh_task_editor_buttons()
|
|
125
|
+
|
|
126
|
+
def _build_copy_paste_tools(self, parent: Any) -> None:
|
|
127
|
+
import tkinter as tk
|
|
128
|
+
from tkinter import ttk
|
|
129
|
+
|
|
130
|
+
group = self._build_collapsible_group(
|
|
131
|
+
parent,
|
|
132
|
+
group_id="copy_paste_tools",
|
|
133
|
+
title="Terminal and standard commands",
|
|
134
|
+
subtitle="local execution helpers",
|
|
135
|
+
)
|
|
136
|
+
body = group.body
|
|
137
|
+
tk.Label(
|
|
138
|
+
body,
|
|
139
|
+
text="Use these helpers when the selected communication mode needs a local command or terminal.",
|
|
140
|
+
bg=THEME.color_panel_bg,
|
|
141
|
+
fg=THEME.color_muted_text,
|
|
142
|
+
font=THEME.small_font,
|
|
143
|
+
anchor=tk.W,
|
|
144
|
+
justify=tk.LEFT,
|
|
145
|
+
wraplength=720,
|
|
146
|
+
).pack(fill=tk.X, pady=(0, 8))
|
|
147
|
+
row = tk.Frame(body, bg=THEME.color_panel_bg)
|
|
148
|
+
row.pack(fill=tk.X)
|
|
149
|
+
self.task_open_terminal_button = ttk.Button(
|
|
150
|
+
row,
|
|
151
|
+
text="Open local terminal",
|
|
152
|
+
command=self.open_transfer_terminal,
|
|
153
|
+
)
|
|
154
|
+
attach_tooltip(
|
|
155
|
+
self.task_open_terminal_button,
|
|
156
|
+
"Open the operating-system terminal for the currently selected transfer mode.",
|
|
157
|
+
)
|
|
158
|
+
self.task_open_terminal_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
159
|
+
self.task_standard_command_button = ttk.Button(
|
|
160
|
+
row,
|
|
161
|
+
text=standard_command_label_for_communication_mode(self.current_communication_mode()),
|
|
162
|
+
command=self.run_mode_standard_command,
|
|
163
|
+
)
|
|
164
|
+
attach_tooltip(
|
|
165
|
+
self.task_standard_command_button,
|
|
166
|
+
(
|
|
167
|
+
"Run the selected mode's standard command: mode a uses "
|
|
168
|
+
"agentic-kit transfer patch-cycle-status --json; mode b uses "
|
|
169
|
+
"agentic-kit transfer continue --json."
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
self.task_standard_command_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
173
|
+
self.refresh_task_editor_buttons()
|
|
174
|
+
|
|
175
|
+
def _build_file_browser(self, parent: Any) -> None:
|
|
176
|
+
import tkinter as tk
|
|
177
|
+
from tkinter import ttk
|
|
178
|
+
|
|
179
|
+
group = self._build_collapsible_group(
|
|
180
|
+
parent,
|
|
181
|
+
group_id="file_browser",
|
|
182
|
+
title="Copy / paste files",
|
|
183
|
+
subtitle="read-only local browser",
|
|
184
|
+
)
|
|
185
|
+
browser_frame = group.body
|
|
186
|
+
header = tk.Frame(browser_frame, bg=THEME.color_panel_bg)
|
|
187
|
+
header.pack(fill=tk.X, pady=(0, 6))
|
|
188
|
+
tk.Label(
|
|
189
|
+
header,
|
|
190
|
+
text="COPY / PASTE FILES",
|
|
191
|
+
bg=THEME.color_panel_bg,
|
|
192
|
+
fg=THEME.color_muted_text,
|
|
193
|
+
font=THEME.section_font,
|
|
194
|
+
anchor=tk.W,
|
|
195
|
+
).pack(side=tk.LEFT)
|
|
196
|
+
tk.Label(
|
|
197
|
+
header,
|
|
198
|
+
text="read-only local browser",
|
|
199
|
+
bg=THEME.color_panel_bg,
|
|
200
|
+
fg=THEME.color_muted_text,
|
|
201
|
+
font=THEME.small_font,
|
|
202
|
+
).pack(side=tk.RIGHT)
|
|
203
|
+
|
|
204
|
+
body = tk.Frame(browser_frame, bg=THEME.color_panel_bg)
|
|
205
|
+
body.pack(fill=tk.X)
|
|
206
|
+
self.file_browser = tk.Listbox(body, height=5, font=THEME.small_font, exportselection=False)
|
|
207
|
+
self.file_browser.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
208
|
+
scrollbar = ttk.Scrollbar(body, orient=tk.VERTICAL, command=self.file_browser.yview)
|
|
209
|
+
self.file_browser.configure(yscrollcommand=scrollbar.set)
|
|
210
|
+
scrollbar.pack(side=tk.LEFT, fill=tk.Y, padx=(4, 8))
|
|
211
|
+
|
|
212
|
+
buttons = tk.Frame(body, bg=THEME.color_panel_bg)
|
|
213
|
+
buttons.pack(side=tk.LEFT, fill=tk.Y)
|
|
214
|
+
refresh = ttk.Button(buttons, text="Refresh files", command=self.refresh_file_browser)
|
|
215
|
+
refresh.pack(fill=tk.X, pady=(0, 5))
|
|
216
|
+
attach_tooltip(refresh, "Refresh the local read-only file list for tmp and handoff files.")
|
|
217
|
+
copy_path = ttk.Button(buttons, text="Copy path", command=self.copy_selected_file_path)
|
|
218
|
+
copy_path.pack(fill=tk.X, pady=(0, 5))
|
|
219
|
+
attach_tooltip(copy_path, "Copy the selected repository-relative path to the clipboard.")
|
|
220
|
+
copy_file = ttk.Button(buttons, text="Copy file", command=self.copy_selected_file_content)
|
|
221
|
+
copy_file.pack(fill=tk.X)
|
|
222
|
+
attach_tooltip(copy_file, "Copy the selected text file content to the clipboard without editing it.")
|
|
223
|
+
|
|
224
|
+
self.file_browser_paths: list[str] = []
|
|
225
|
+
self.refresh_file_browser(write_status=False)
|
|
226
|
+
|
|
227
|
+
def _file_browser_roots(self) -> tuple[Path, ...]:
|
|
228
|
+
try:
|
|
229
|
+
workspace = load_workspace(self.project_root)
|
|
230
|
+
except RuntimeError:
|
|
231
|
+
return ()
|
|
232
|
+
return (
|
|
233
|
+
workspace.tmp(),
|
|
234
|
+
workspace.handoff_dir(),
|
|
235
|
+
workspace.handoff_packages_latest(),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _iter_file_browser_paths(self) -> tuple[str, ...]:
|
|
239
|
+
paths: list[str] = []
|
|
240
|
+
for root in self._file_browser_roots():
|
|
241
|
+
if not root.exists() or not root.is_dir():
|
|
242
|
+
continue
|
|
243
|
+
for path in sorted(root.rglob("*")):
|
|
244
|
+
if path.is_symlink() or not path.is_file():
|
|
245
|
+
continue
|
|
246
|
+
try:
|
|
247
|
+
rel = path.relative_to(self.project_root).as_posix()
|
|
248
|
+
except ValueError:
|
|
249
|
+
continue
|
|
250
|
+
paths.append(rel)
|
|
251
|
+
return tuple(paths[:300])
|
|
252
|
+
|
|
253
|
+
def refresh_file_browser(self, *, write_status: bool = True) -> None:
|
|
254
|
+
paths = list(self._iter_file_browser_paths())
|
|
255
|
+
self.file_browser_paths = paths
|
|
256
|
+
self.file_browser.delete(0, "end")
|
|
257
|
+
for path in paths:
|
|
258
|
+
self.file_browser.insert("end", path)
|
|
259
|
+
if write_status:
|
|
260
|
+
self.write_output(f"\nFile browser refreshed: {len(paths)} local files listed.\n")
|
|
261
|
+
|
|
262
|
+
def _selected_file_browser_path(self) -> Path | None:
|
|
263
|
+
selection = self.file_browser.curselection()
|
|
264
|
+
if not selection:
|
|
265
|
+
self.write_output("\nSelect a file in the Copy / Paste Files browser first.\n")
|
|
266
|
+
return None
|
|
267
|
+
index = int(selection[0])
|
|
268
|
+
try:
|
|
269
|
+
relative = self.file_browser_paths[index]
|
|
270
|
+
except IndexError:
|
|
271
|
+
self.write_output("\nSelected file is no longer available. Refresh files and try again.\n")
|
|
272
|
+
return None
|
|
273
|
+
path = (self.project_root / relative).resolve()
|
|
274
|
+
try:
|
|
275
|
+
path.relative_to(self.project_root.resolve())
|
|
276
|
+
except ValueError:
|
|
277
|
+
self.write_output("\nBlocked: selected path is outside the project root.\n")
|
|
278
|
+
return None
|
|
279
|
+
return path
|
|
280
|
+
|
|
281
|
+
def copy_selected_file_path(self) -> None:
|
|
282
|
+
path = self._selected_file_browser_path()
|
|
283
|
+
if path is None:
|
|
284
|
+
return
|
|
285
|
+
relative = path.relative_to(self.project_root).as_posix()
|
|
286
|
+
self.root.clipboard_clear()
|
|
287
|
+
self.root.clipboard_append(relative)
|
|
288
|
+
if hasattr(self.root, "update"):
|
|
289
|
+
self.root.update()
|
|
290
|
+
self.write_output(f"\nCopied path: {relative}\n")
|
|
291
|
+
|
|
292
|
+
def copy_selected_file_content(self) -> None:
|
|
293
|
+
path = self._selected_file_browser_path()
|
|
294
|
+
if path is None:
|
|
295
|
+
return
|
|
296
|
+
relative = path.relative_to(self.project_root).as_posix()
|
|
297
|
+
try:
|
|
298
|
+
content = path.read_text(encoding="utf-8")
|
|
299
|
+
except UnicodeDecodeError:
|
|
300
|
+
self.write_output(f"\nBlocked: {relative} is not a UTF-8 text file.\n")
|
|
301
|
+
return
|
|
302
|
+
except OSError as exc:
|
|
303
|
+
self.write_output(f"\nBlocked: could not read {relative}: {exc}\n")
|
|
304
|
+
return
|
|
305
|
+
if len(content) > 200_000:
|
|
306
|
+
self.write_output(f"\nBlocked: {relative} is too large to copy safely from the GUI.\n")
|
|
307
|
+
return
|
|
308
|
+
self.root.clipboard_clear()
|
|
309
|
+
self.root.clipboard_append(content)
|
|
310
|
+
if hasattr(self.root, "update"):
|
|
311
|
+
self.root.update()
|
|
312
|
+
self.write_output(f"\nCopied file content: {relative}\n")
|
|
313
|
+
|
|
314
|
+
def _build_output_panel(self, parent: Any) -> None:
|
|
315
|
+
import tkinter as tk
|
|
316
|
+
from tkinter import ttk
|
|
317
|
+
|
|
318
|
+
output_frame = tk.Frame(
|
|
319
|
+
parent,
|
|
320
|
+
bg=THEME.color_panel_bg,
|
|
321
|
+
highlightbackground=THEME.color_border,
|
|
322
|
+
highlightthickness=1,
|
|
323
|
+
padx=THEME.section_padding,
|
|
324
|
+
pady=THEME.section_padding,
|
|
325
|
+
)
|
|
326
|
+
self._register_group_frame("output", output_frame)
|
|
327
|
+
output_frame.pack(fill=tk.BOTH, expand=True)
|
|
328
|
+
output_header = tk.Frame(output_frame, bg=THEME.color_panel_bg)
|
|
329
|
+
output_header.pack(fill=tk.X, pady=(0, 7))
|
|
330
|
+
tk.Label(
|
|
331
|
+
output_header,
|
|
332
|
+
text="□ ACTIVITY",
|
|
333
|
+
bg=THEME.color_panel_bg,
|
|
334
|
+
fg=THEME.color_muted_text,
|
|
335
|
+
font=THEME.section_font,
|
|
336
|
+
anchor=tk.W,
|
|
337
|
+
).pack(side=tk.LEFT)
|
|
338
|
+
self.busy_status_var = tk.StringVar(value="Ready")
|
|
339
|
+
tk.Label(
|
|
340
|
+
output_header,
|
|
341
|
+
textvariable=self.busy_status_var,
|
|
342
|
+
bg=THEME.color_panel_bg,
|
|
343
|
+
fg=THEME.color_muted_text,
|
|
344
|
+
font=THEME.small_font,
|
|
345
|
+
).pack(side=tk.LEFT, padx=(10, 0))
|
|
346
|
+
self.busy_progress = ttk.Progressbar(output_header, mode="indeterminate", length=120)
|
|
347
|
+
self.busy_progress.pack(side=tk.LEFT, padx=(8, 0))
|
|
348
|
+
clear_button = ttk.Button(output_header, text="Clear", command=self.clear_activity_log)
|
|
349
|
+
attach_tooltip(clear_button, "Clear the local cockpit activity history.")
|
|
350
|
+
clear_button.pack(side=tk.RIGHT)
|
|
351
|
+
logs_button = ttk.Button(output_header, text="Open logs folder", command=self.open_logs_folder)
|
|
352
|
+
attach_tooltip(logs_button, "Open the local repository tmp/ folder in the operating-system file manager.")
|
|
353
|
+
logs_button.pack(side=tk.RIGHT, padx=(0, 8))
|
|
354
|
+
|
|
355
|
+
# This is a local cockpit action/result history, not an LLM chat transcript.
|
|
356
|
+
activity_shell = tk.Frame(output_frame, bg=THEME.color_panel_bg)
|
|
357
|
+
activity_shell.pack(fill=tk.BOTH, expand=True)
|
|
358
|
+
self.activity_canvas = tk.Canvas(
|
|
359
|
+
activity_shell,
|
|
360
|
+
bg=THEME.color_panel_bg,
|
|
361
|
+
height=THEME.output_height * 22,
|
|
362
|
+
highlightthickness=0,
|
|
363
|
+
)
|
|
364
|
+
self.activity_scrollbar = ttk.Scrollbar(
|
|
365
|
+
activity_shell,
|
|
366
|
+
orient=tk.VERTICAL,
|
|
367
|
+
command=self.activity_canvas.yview,
|
|
368
|
+
)
|
|
369
|
+
self.activity_canvas.configure(yscrollcommand=self.activity_scrollbar.set)
|
|
370
|
+
self.activity_container = tk.Frame(
|
|
371
|
+
self.activity_canvas,
|
|
372
|
+
bg=THEME.color_panel_bg,
|
|
373
|
+
)
|
|
374
|
+
self.activity_window = self.activity_canvas.create_window(
|
|
375
|
+
(0, 0),
|
|
376
|
+
window=self.activity_container,
|
|
377
|
+
anchor=tk.NW,
|
|
378
|
+
)
|
|
379
|
+
self.activity_container.bind(
|
|
380
|
+
"<Configure>",
|
|
381
|
+
lambda _event: self.activity_canvas.configure(scrollregion=self.activity_canvas.bbox(tk.ALL)),
|
|
382
|
+
)
|
|
383
|
+
self.activity_canvas.bind(
|
|
384
|
+
"<Configure>",
|
|
385
|
+
lambda event: self.activity_canvas.itemconfigure(
|
|
386
|
+
self.activity_window,
|
|
387
|
+
width=getattr(event, "width", 760),
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
self._bind_activity_mousewheel()
|
|
391
|
+
self.activity_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
392
|
+
self.activity_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
393
|
+
self.render_activity_log()
|
|
394
|
+
|
|
395
|
+
def _bind_activity_mousewheel(self) -> None:
|
|
396
|
+
for event_name in ("<MouseWheel>", "<Button-4>", "<Button-5>"):
|
|
397
|
+
self.activity_canvas.bind(event_name, self._on_activity_mousewheel)
|
|
398
|
+
|
|
399
|
+
def _on_activity_mousewheel(self, event: Any) -> str:
|
|
400
|
+
number = getattr(event, "num", None)
|
|
401
|
+
delta = getattr(event, "delta", 0)
|
|
402
|
+
if number == 4:
|
|
403
|
+
units = -1
|
|
404
|
+
elif number == 5:
|
|
405
|
+
units = 1
|
|
406
|
+
else:
|
|
407
|
+
units = -1 if delta > 0 else 1
|
|
408
|
+
self.activity_canvas.yview_scroll(units, "units")
|
|
409
|
+
return "break"
|
|
410
|
+
|
|
411
|
+
def _activity_status_colors(self, status: str) -> tuple[str, str]:
|
|
412
|
+
return {
|
|
413
|
+
"PASS": (THEME.color_ready_bg, "#006b50"),
|
|
414
|
+
"BLOCKED": (THEME.color_bounded, "#6b3d00"),
|
|
415
|
+
"ERROR": (THEME.color_destructive, "#8a1f11"),
|
|
416
|
+
"INFO": ("#f2f2f2", "#4a4a4a"),
|
|
417
|
+
}.get(status, ("#f2f2f2", "#4a4a4a"))
|
|
418
|
+
|
|
419
|
+
def render_activity_log(self) -> None:
|
|
420
|
+
for child in self.activity_container.winfo_children():
|
|
421
|
+
child.destroy()
|
|
422
|
+
for entry in self.activity_log.entries():
|
|
423
|
+
self._render_activity_entry(entry)
|
|
424
|
+
self.activity_canvas.configure(scrollregion=self.activity_canvas.bbox("all"))
|
|
425
|
+
if hasattr(self.activity_canvas, "yview_moveto"):
|
|
426
|
+
self.activity_canvas.yview_moveto(1.0)
|
|
427
|
+
|
|
428
|
+
def _render_activity_entry(self, entry: ActivityEntry) -> None:
|
|
429
|
+
import tkinter as tk
|
|
430
|
+
from tkinter import ttk
|
|
431
|
+
|
|
432
|
+
row = tk.Frame(self.activity_container, bg=THEME.color_panel_bg)
|
|
433
|
+
row._activity_actor = entry.actor
|
|
434
|
+
row._activity_status = entry.status
|
|
435
|
+
row.pack(fill=tk.X, pady=(0, 7))
|
|
436
|
+
if entry.actor == "user":
|
|
437
|
+
bubble = tk.Frame(row, bg=THEME.color_recommended_bg, padx=10, pady=6)
|
|
438
|
+
bubble._activity_actor = "user"
|
|
439
|
+
bubble._activity_anchor = "e"
|
|
440
|
+
bubble.pack(side=tk.RIGHT, anchor=tk.E)
|
|
441
|
+
tk.Label(
|
|
442
|
+
bubble,
|
|
443
|
+
text=f"You ran: {entry.label}",
|
|
444
|
+
bg=THEME.color_recommended_bg,
|
|
445
|
+
fg="#174ea6",
|
|
446
|
+
font=THEME.body_font,
|
|
447
|
+
anchor=tk.E,
|
|
448
|
+
justify=tk.RIGHT,
|
|
449
|
+
wraplength=520,
|
|
450
|
+
).pack(side=tk.RIGHT)
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
bubble = tk.Frame(
|
|
454
|
+
row,
|
|
455
|
+
bg="#f7f7f7",
|
|
456
|
+
highlightbackground=THEME.color_border,
|
|
457
|
+
highlightthickness=1,
|
|
458
|
+
padx=9,
|
|
459
|
+
pady=7,
|
|
460
|
+
)
|
|
461
|
+
bubble._activity_actor = "kit"
|
|
462
|
+
bubble._activity_anchor = "w"
|
|
463
|
+
bubble._activity_status = entry.status
|
|
464
|
+
bubble.pack(side=tk.LEFT, anchor=tk.W, fill=tk.X, expand=True)
|
|
465
|
+
header = tk.Frame(bubble, bg="#f7f7f7")
|
|
466
|
+
header.pack(fill=tk.X, pady=(0, 5))
|
|
467
|
+
tk.Label(
|
|
468
|
+
header,
|
|
469
|
+
text=f"{entry.created_at_utc} · {entry.label}",
|
|
470
|
+
bg="#f7f7f7",
|
|
471
|
+
fg=THEME.color_muted_text,
|
|
472
|
+
font=THEME.small_font,
|
|
473
|
+
anchor=tk.W,
|
|
474
|
+
).pack(side=tk.LEFT)
|
|
475
|
+
badge_bg, badge_fg = self._activity_status_colors(entry.status)
|
|
476
|
+
badge = tk.Label(
|
|
477
|
+
header,
|
|
478
|
+
text=entry.status,
|
|
479
|
+
bg=badge_bg,
|
|
480
|
+
fg=badge_fg,
|
|
481
|
+
font=THEME.small_font,
|
|
482
|
+
padx=6,
|
|
483
|
+
pady=2,
|
|
484
|
+
)
|
|
485
|
+
badge._activity_status_badge = entry.status
|
|
486
|
+
badge.pack(side=tk.RIGHT)
|
|
487
|
+
tk.Label(
|
|
488
|
+
bubble,
|
|
489
|
+
text=entry.body,
|
|
490
|
+
bg="#f7f7f7",
|
|
491
|
+
fg="#1f1f1f",
|
|
492
|
+
font=THEME.output_font,
|
|
493
|
+
anchor=tk.W,
|
|
494
|
+
justify=tk.LEFT,
|
|
495
|
+
wraplength=760,
|
|
496
|
+
).pack(fill=tk.X)
|
|
497
|
+
copy_button = ttk.Button(
|
|
498
|
+
bubble,
|
|
499
|
+
text="Copy",
|
|
500
|
+
command=lambda body=entry.body: self.copy_activity_body(body),
|
|
501
|
+
)
|
|
502
|
+
copy_button._activity_copy_body = entry.body
|
|
503
|
+
attach_tooltip(copy_button, "Copy only this result body to the clipboard.")
|
|
504
|
+
copy_button.pack(anchor=tk.W, pady=(6, 0))
|
|
505
|
+
|
|
506
|
+
def log_action(self, label: str) -> None:
|
|
507
|
+
self.activity_log.log_action(label)
|
|
508
|
+
self.render_activity_log()
|
|
509
|
+
|
|
510
|
+
def log_result(self, label: str, status: str | None, body: str) -> None:
|
|
511
|
+
self.activity_log.log_result(label, status, body)
|
|
512
|
+
self.render_activity_log()
|
|
513
|
+
|
|
514
|
+
def write_output(self, text: str) -> None:
|
|
515
|
+
if hasattr(self, "activity_log") and hasattr(self, "activity_container"):
|
|
516
|
+
self.log_result("Output", None, text)
|
|
517
|
+
return
|
|
518
|
+
output = getattr(self, "output", None)
|
|
519
|
+
if output is not None:
|
|
520
|
+
output.insert("end", text)
|
|
521
|
+
output.see("end")
|
|
522
|
+
|
|
523
|
+
def _completed_status(self, completed: subprocess.CompletedProcess[str]) -> str:
|
|
524
|
+
if completed.stdout:
|
|
525
|
+
try:
|
|
526
|
+
payload = json.loads(completed.stdout)
|
|
527
|
+
except ValueError:
|
|
528
|
+
payload = None
|
|
529
|
+
if isinstance(payload, dict):
|
|
530
|
+
raw_status = payload.get("result_status", payload.get("STATE", payload.get("state")))
|
|
531
|
+
if raw_status is not None:
|
|
532
|
+
return str(raw_status)
|
|
533
|
+
return "PASS" if completed.returncode == 0 else "ERROR"
|
|
534
|
+
|
|
535
|
+
def clear_activity_log(self) -> None:
|
|
536
|
+
self.activity_log.clear()
|
|
537
|
+
self.render_activity_log()
|
|
538
|
+
|
|
539
|
+
def clear_output(self) -> None:
|
|
540
|
+
self.clear_activity_log()
|
|
541
|
+
|
|
542
|
+
def _activity_transcript(self) -> str:
|
|
543
|
+
return "\n\n".join(entry.body for entry in self.activity_log.entries())
|
|
544
|
+
|
|
545
|
+
def copy_activity_body(self, body: str) -> None:
|
|
546
|
+
self.root.clipboard_clear()
|
|
547
|
+
self.root.clipboard_append(body)
|
|
548
|
+
if hasattr(self.root, "update"):
|
|
549
|
+
self.root.update()
|
|
550
|
+
|
|
551
|
+
def copy_output(self) -> None:
|
|
552
|
+
self.copy_activity_body(self._activity_transcript())
|
|
553
|
+
|
|
554
|
+
def open_logs_folder(self) -> None:
|
|
555
|
+
workspace = load_workspace(self.project_root)
|
|
556
|
+
target = workspace.tmp()
|
|
557
|
+
try:
|
|
558
|
+
open_folder_in_file_manager(target)
|
|
559
|
+
except OSError as exc:
|
|
560
|
+
self.log_result("Open logs folder", "ERROR", f"Could not open logs folder: {exc}")
|
|
561
|
+
return
|
|
562
|
+
self.log_result("Open logs folder", "INFO", f"Opened logs folder: {workspace.path_text(target)}/")
|
|
563
|
+
|
|
564
|
+
def _set_busy(self, text: str, *, running: bool) -> None:
|
|
565
|
+
busy_var = getattr(self, "busy_status_var", None)
|
|
566
|
+
progress = getattr(self, "busy_progress", None)
|
|
567
|
+
if busy_var is not None:
|
|
568
|
+
busy_var.set(text)
|
|
569
|
+
if progress is not None:
|
|
570
|
+
if running:
|
|
571
|
+
progress.start(8)
|
|
572
|
+
else:
|
|
573
|
+
progress.stop()
|
|
574
|
+
if hasattr(self.root, "update_idletasks"):
|
|
575
|
+
self.root.update_idletasks()
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def current_task_body(self) -> str:
|
|
579
|
+
if self.task_text is None:
|
|
580
|
+
return ""
|
|
581
|
+
return self.task_text.get("1.0", "end").strip()
|
|
582
|
+
|
|
583
|
+
def refresh_task_editor_buttons(self, _event: object | None = None) -> None:
|
|
584
|
+
if self.task_send_button is None or self.task_read_button is None:
|
|
585
|
+
return
|
|
586
|
+
mode = self.current_communication_mode()
|
|
587
|
+
if self.task_standard_command_button is not None:
|
|
588
|
+
command_args = standard_command_args_for_communication_mode(mode)
|
|
589
|
+
self.task_standard_command_button.configure(
|
|
590
|
+
text=standard_command_label_for_communication_mode(mode),
|
|
591
|
+
state="normal" if command_args else "disabled",
|
|
592
|
+
)
|
|
593
|
+
if self.task_open_terminal_button is not None:
|
|
594
|
+
self.task_open_terminal_button.configure(state="normal")
|
|
595
|
+
can_send = task_editor_send_enabled(
|
|
596
|
+
self.current_task_body(),
|
|
597
|
+
traffic_light_state=self.basic_view.traffic_light_state,
|
|
598
|
+
communication_context_fresh=self.basic_view.communication_context_fresh,
|
|
599
|
+
required_next_reply=self.basic_view.required_next_reply,
|
|
600
|
+
)
|
|
601
|
+
if self.task_editor_state == TaskEditorState.SENT:
|
|
602
|
+
self.task_send_button.configure(state="disabled")
|
|
603
|
+
self.task_read_button.configure(state="normal")
|
|
604
|
+
self.task_status_var.set(
|
|
605
|
+
f"Task carrier published to gui-transfer-tasks as mode {mode}. Send g/go in chat."
|
|
606
|
+
)
|
|
607
|
+
return
|
|
608
|
+
if self.task_editor_state == TaskEditorState.BLOCKED:
|
|
609
|
+
self.task_read_button.configure(state="disabled")
|
|
610
|
+
self.task_send_button.configure(state="normal" if can_send else "disabled")
|
|
611
|
+
if self.basic_view.required_next_reply == "d2":
|
|
612
|
+
self.task_status_var.set("Blocked: send d2 and complete communication-rule ACK before mutation.")
|
|
613
|
+
elif not self.current_task_body():
|
|
614
|
+
self.task_status_var.set("Blocked: write a GUI task before sending.")
|
|
615
|
+
else:
|
|
616
|
+
self.task_status_var.set("Publish failed or is blocked. Inspect the Send output before retrying.")
|
|
617
|
+
return
|
|
618
|
+
self.task_read_button.configure(state="disabled")
|
|
619
|
+
self.task_send_button.configure(state="normal" if can_send else "disabled")
|
|
620
|
+
if command_description := standard_command_description_for_communication_mode(mode):
|
|
621
|
+
self.task_status_var.set(command_description)
|
|
622
|
+
if self.basic_view.required_next_reply == "d2":
|
|
623
|
+
self.task_status_var.set("Blocked: send d2 and complete communication-rule ACK before mutation.")
|
|
624
|
+
elif not self.current_task_body():
|
|
625
|
+
self.task_status_var.set("Write a GUI task before sending.")
|
|
626
|
+
elif not can_send:
|
|
627
|
+
self.task_status_var.set("Blocked: gatekeeper must be READY and communication context fresh.")
|
|
628
|
+
else:
|
|
629
|
+
self.task_status_var.set("Ready to publish the GUI task carrier.")
|
|
630
|
+
|
|
631
|
+
def _agentic_command(self, *parts: str) -> subprocess.CompletedProcess[str]:
|
|
632
|
+
executable = self.project_root / ".venv" / "bin" / "agentic-kit"
|
|
633
|
+
command = [str(executable) if executable.exists() else "agentic-kit", *parts]
|
|
634
|
+
label = "agentic-kit " + " ".join(parts[:3])
|
|
635
|
+
self._set_busy(f"Running: {label}", running=True)
|
|
636
|
+
try:
|
|
637
|
+
completed = subprocess.run(
|
|
638
|
+
command,
|
|
639
|
+
cwd=self.project_root,
|
|
640
|
+
text=True,
|
|
641
|
+
stdout=subprocess.PIPE,
|
|
642
|
+
stderr=subprocess.PIPE,
|
|
643
|
+
check=False,
|
|
644
|
+
)
|
|
645
|
+
except OSError as exc:
|
|
646
|
+
completed = subprocess.CompletedProcess(command, 127, "", str(exc))
|
|
647
|
+
self._set_busy("Done" if completed.returncode == 0 else "Blocked", running=False)
|
|
648
|
+
return completed
|
|
649
|
+
|
|
650
|
+
def show_initial_llm_prompt(self) -> None:
|
|
651
|
+
self.log_action("Initial prompt")
|
|
652
|
+
completed = self._agentic_command("gui", "initial-llm-prompt", "--root", str(self.project_root), "--json")
|
|
653
|
+
self.log_result("Initial prompt", self._completed_status(completed), completed.stdout or completed.stderr)
|
|
654
|
+
|
|
655
|
+
def open_transfer_terminal(self) -> None:
|
|
656
|
+
self.log_action("Open local terminal")
|
|
657
|
+
plan = open_transfer_terminal_for_project(
|
|
658
|
+
self.project_root,
|
|
659
|
+
communication_mode=self.current_communication_mode(),
|
|
660
|
+
)
|
|
661
|
+
self.log_result("Open local terminal", "INFO", json.dumps(plan.as_json_data(), indent=2, sort_keys=True))
|
|
662
|
+
|
|
663
|
+
def run_mode_standard_command(self) -> None:
|
|
664
|
+
self.log_action(standard_command_label_for_communication_mode(self.current_communication_mode()))
|
|
665
|
+
command_args = standard_command_args_for_communication_mode(self.current_communication_mode())
|
|
666
|
+
if not command_args:
|
|
667
|
+
self.log_result("Standard command", "INFO", "No standard command is defined for Copy-and-Paste recovery mode.")
|
|
668
|
+
return
|
|
669
|
+
completed = self._agentic_command(*command_args)
|
|
670
|
+
self.log_result("Standard command", self._completed_status(completed), completed.stdout or completed.stderr)
|
|
671
|
+
|
|
672
|
+
def send_user_task(self) -> None:
|
|
673
|
+
self.log_action("Send")
|
|
674
|
+
body = self.current_task_body()
|
|
675
|
+
if not task_editor_send_enabled(
|
|
676
|
+
body,
|
|
677
|
+
traffic_light_state=self.basic_view.traffic_light_state,
|
|
678
|
+
communication_context_fresh=self.basic_view.communication_context_fresh,
|
|
679
|
+
required_next_reply=self.basic_view.required_next_reply,
|
|
680
|
+
):
|
|
681
|
+
self.task_editor_state = TaskEditorState.BLOCKED
|
|
682
|
+
self.task_status_var.set("Blocked: task text, READY state, and fresh communication context are required.")
|
|
683
|
+
self.log_result(
|
|
684
|
+
"Send",
|
|
685
|
+
"BLOCKED",
|
|
686
|
+
"Blocked: task text, READY state, and fresh communication context are required.",
|
|
687
|
+
)
|
|
688
|
+
self.refresh_task_editor_buttons()
|
|
689
|
+
return
|
|
690
|
+
tmp_path = self.project_root / "tmp" / "gui-task-editor-current-task.md"
|
|
691
|
+
tmp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
692
|
+
tmp_path.write_text(body + "\n", encoding="utf-8")
|
|
693
|
+
completed = self._agentic_command(
|
|
694
|
+
"transfer",
|
|
695
|
+
"submit-user-task",
|
|
696
|
+
"--title",
|
|
697
|
+
"GUI file-transfer task",
|
|
698
|
+
"--body-file",
|
|
699
|
+
str(tmp_path),
|
|
700
|
+
"--communication-mode",
|
|
701
|
+
self.current_communication_mode(),
|
|
702
|
+
"--publish",
|
|
703
|
+
"--json",
|
|
704
|
+
)
|
|
705
|
+
self.log_result("Send", self._completed_status(completed), completed.stdout or completed.stderr)
|
|
706
|
+
result_status = "PASS" if completed.returncode == 0 else "FAIL"
|
|
707
|
+
remote_readable = False
|
|
708
|
+
if completed.stdout:
|
|
709
|
+
try:
|
|
710
|
+
payload = __import__("json").loads(completed.stdout)
|
|
711
|
+
remote_readable = bool(payload.get("remote_readable"))
|
|
712
|
+
except ValueError:
|
|
713
|
+
remote_readable = False
|
|
714
|
+
self.task_editor_state = task_editor_state_after_send(
|
|
715
|
+
result_status,
|
|
716
|
+
remote_readable=remote_readable,
|
|
717
|
+
)
|
|
718
|
+
if self.task_editor_state == TaskEditorState.SENT and self.task_text is not None:
|
|
719
|
+
self.task_text.configure(state="disabled")
|
|
720
|
+
self.refresh_task_editor_buttons()
|
|
721
|
+
|
|
722
|
+
def read_last_task_result(self) -> None:
|
|
723
|
+
self.log_action("Read")
|
|
724
|
+
completed = self._agentic_command("transfer", "state", "--json")
|
|
725
|
+
self.log_result("Read", self._completed_status(completed), completed.stdout or completed.stderr)
|
|
726
|
+
outbox_available = False
|
|
727
|
+
if completed.stdout:
|
|
728
|
+
try:
|
|
729
|
+
payload = __import__("json").loads(completed.stdout)
|
|
730
|
+
outbox_available = transfer_state_has_canonical_outbox_result(payload)
|
|
731
|
+
except ValueError:
|
|
732
|
+
outbox_available = False
|
|
733
|
+
if completed.returncode == 0 and not outbox_available:
|
|
734
|
+
self.task_editor_state = TaskEditorState.SENT
|
|
735
|
+
self.task_status_var.set(
|
|
736
|
+
f"No canonical transfer result yet at {CANONICAL_TRANSFER_OUTBOX_PATH.as_posix()}."
|
|
737
|
+
)
|
|
738
|
+
if self.task_send_button is not None:
|
|
739
|
+
self.task_send_button.configure(state="disabled")
|
|
740
|
+
if self.task_read_button is not None:
|
|
741
|
+
self.task_read_button.configure(state="normal")
|
|
742
|
+
return
|
|
743
|
+
result_status = "PASS" if completed.returncode == 0 and outbox_available else "FAIL"
|
|
744
|
+
self.task_editor_state = task_editor_state_after_read(result_status)
|
|
745
|
+
if self.task_editor_state == TaskEditorState.IDLE and self.task_text is not None:
|
|
746
|
+
self.task_text.configure(state="normal")
|
|
747
|
+
self.task_text.delete("1.0", "end")
|
|
748
|
+
self.refresh_task_editor_buttons()
|