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.
Files changed (310) hide show
  1. agentic_project_kit/__init__.py +1 -0
  2. agentic_project_kit/absolute_path_portability_audit.py +241 -0
  3. agentic_project_kit/access_level_policy.py +14 -0
  4. agentic_project_kit/access_levels.py +22 -0
  5. agentic_project_kit/action_registry.py +195 -0
  6. agentic_project_kit/action_specs.py +286 -0
  7. agentic_project_kit/agent_command_runner.py +516 -0
  8. agentic_project_kit/audit_test_matrix.py +36 -0
  9. agentic_project_kit/audit_test_matrix_cli.py +43 -0
  10. agentic_project_kit/chat_bootloader.py +257 -0
  11. agentic_project_kit/chat_entrypoint_contract.py +297 -0
  12. agentic_project_kit/checks.py +511 -0
  13. agentic_project_kit/ci_readiness.py +211 -0
  14. agentic_project_kit/cli.py +147 -0
  15. agentic_project_kit/cli_commands/__init__.py +1 -0
  16. agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
  17. agentic_project_kit/cli_commands/actions.py +21 -0
  18. agentic_project_kit/cli_commands/artifact_gc.py +240 -0
  19. agentic_project_kit/cli_commands/boot.py +55 -0
  20. agentic_project_kit/cli_commands/chat.py +34 -0
  21. agentic_project_kit/cli_commands/checks.py +305 -0
  22. agentic_project_kit/cli_commands/cockpit.py +103 -0
  23. agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
  24. agentic_project_kit/cli_commands/commands.py +129 -0
  25. agentic_project_kit/cli_commands/dev.py +23 -0
  26. agentic_project_kit/cli_commands/direction.py +92 -0
  27. agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
  28. agentic_project_kit/cli_commands/doc_registry.py +126 -0
  29. agentic_project_kit/cli_commands/docs.py +223 -0
  30. agentic_project_kit/cli_commands/dpa.py +1338 -0
  31. agentic_project_kit/cli_commands/evidence.py +251 -0
  32. agentic_project_kit/cli_commands/github.py +34 -0
  33. agentic_project_kit/cli_commands/governance.py +14 -0
  34. agentic_project_kit/cli_commands/gui.py +23 -0
  35. agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
  36. agentic_project_kit/cli_commands/handoff.py +183 -0
  37. agentic_project_kit/cli_commands/human_workflows.py +430 -0
  38. agentic_project_kit/cli_commands/init.py +132 -0
  39. agentic_project_kit/cli_commands/instruction.py +75 -0
  40. agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
  41. agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
  42. agentic_project_kit/cli_commands/pass_already_done.py +66 -0
  43. agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
  44. agentic_project_kit/cli_commands/patch_preflight.py +37 -0
  45. agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
  46. agentic_project_kit/cli_commands/patterns.py +40 -0
  47. agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
  48. agentic_project_kit/cli_commands/pr.py +133 -0
  49. agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
  50. agentic_project_kit/cli_commands/profiles.py +26 -0
  51. agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
  52. agentic_project_kit/cli_commands/project_direction.py +25 -0
  53. agentic_project_kit/cli_commands/release.py +392 -0
  54. agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
  55. agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
  56. agentic_project_kit/cli_commands/remote_next.py +57 -0
  57. agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
  58. agentic_project_kit/cli_commands/rule_registry.py +129 -0
  59. agentic_project_kit/cli_commands/rules.py +222 -0
  60. agentic_project_kit/cli_commands/scaffold.py +46 -0
  61. agentic_project_kit/cli_commands/slice.py +21 -0
  62. agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
  63. agentic_project_kit/cli_commands/state.py +51 -0
  64. agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
  65. agentic_project_kit/cli_commands/todo.py +53 -0
  66. agentic_project_kit/cli_commands/transfer.py +19 -0
  67. agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
  68. agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
  69. agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
  70. agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
  71. agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
  72. agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
  73. agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
  74. agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
  75. agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
  76. agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
  77. agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
  78. agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
  79. agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
  80. agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
  81. agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
  82. agentic_project_kit/cli_commands/transfer_shared.py +274 -0
  83. agentic_project_kit/cli_commands/validation.py +200 -0
  84. agentic_project_kit/cli_commands/version.py +27 -0
  85. agentic_project_kit/cli_commands/work_orders.py +219 -0
  86. agentic_project_kit/cli_commands/workflow.py +471 -0
  87. agentic_project_kit/cli_commands/workflow_guard.py +26 -0
  88. agentic_project_kit/cli_commands/workspace.py +252 -0
  89. agentic_project_kit/cockpit.py +608 -0
  90. agentic_project_kit/cockpit_readiness.py +67 -0
  91. agentic_project_kit/command_authority_audit.py +192 -0
  92. agentic_project_kit/command_inbox_check.py +103 -0
  93. agentic_project_kit/command_manifest.py +591 -0
  94. agentic_project_kit/command_queue_contract.py +161 -0
  95. agentic_project_kit/command_selector.py +245 -0
  96. agentic_project_kit/command_taxonomy.py +264 -0
  97. agentic_project_kit/commit_guard.py +67 -0
  98. agentic_project_kit/communication_artifact_gc.py +495 -0
  99. agentic_project_kit/communication_rule_context.py +437 -0
  100. agentic_project_kit/communication_state.py +115 -0
  101. agentic_project_kit/composed_wrapper_invariants.py +97 -0
  102. agentic_project_kit/composite_short_circuit.py +140 -0
  103. agentic_project_kit/contract.py +251 -0
  104. agentic_project_kit/control_state.py +83 -0
  105. agentic_project_kit/doc_currency_audit.py +536 -0
  106. agentic_project_kit/doc_lifecycle.py +1179 -0
  107. agentic_project_kit/doc_lifecycle_signals.py +446 -0
  108. agentic_project_kit/doc_lifecycle_sweep.py +639 -0
  109. agentic_project_kit/doc_mesh.py +491 -0
  110. agentic_project_kit/doctor.py +379 -0
  111. agentic_project_kit/documentation_registry.py +1122 -0
  112. agentic_project_kit/documentation_system_audit.py +310 -0
  113. agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
  114. agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
  115. agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
  116. agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
  117. agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
  118. agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
  119. agentic_project_kit/dpa_final_closeout.py +627 -0
  120. agentic_project_kit/dpa_fixture_evidence.py +680 -0
  121. agentic_project_kit/dpa_maintainer_record_check.py +711 -0
  122. agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
  123. agentic_project_kit/dpa_probe_002_readiness.py +509 -0
  124. agentic_project_kit/dpa_probe_003_readiness.py +417 -0
  125. agentic_project_kit/dpa_probe_004_readiness.py +437 -0
  126. agentic_project_kit/dpa_readiness.py +505 -0
  127. agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
  128. agentic_project_kit/dpa_registry_contracts.py +312 -0
  129. agentic_project_kit/dpa_renderer_readiness.py +407 -0
  130. agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
  131. agentic_project_kit/dpa_stable_readiness.py +743 -0
  132. agentic_project_kit/dpa_successor_projection.py +168 -0
  133. agentic_project_kit/dpa_workspace_init_projection.py +93 -0
  134. agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
  135. agentic_project_kit/entrypoint_slice_runner.py +153 -0
  136. agentic_project_kit/evidence_clean.py +152 -0
  137. agentic_project_kit/evidence_commit_paths.py +214 -0
  138. agentic_project_kit/evidence_finalize_log.py +204 -0
  139. agentic_project_kit/evidence_guard.py +114 -0
  140. agentic_project_kit/evidence_inspector.py +507 -0
  141. agentic_project_kit/evidence_state_contract.py +99 -0
  142. agentic_project_kit/execution_mode_state.py +148 -0
  143. agentic_project_kit/final_summary_contract.py +85 -0
  144. agentic_project_kit/finalize_guard.py +139 -0
  145. agentic_project_kit/gatekeeper_core.py +232 -0
  146. agentic_project_kit/github.py +25 -0
  147. agentic_project_kit/governance.py +59 -0
  148. agentic_project_kit/gui_action_execution.py +105 -0
  149. agentic_project_kit/gui_action_renderer.py +81 -0
  150. agentic_project_kit/gui_action_run_intent.py +98 -0
  151. agentic_project_kit/gui_action_views.py +195 -0
  152. agentic_project_kit/gui_activity_log.py +76 -0
  153. agentic_project_kit/gui_button_catalog.py +605 -0
  154. agentic_project_kit/gui_cockpit.py +365 -0
  155. agentic_project_kit/gui_cockpit_actions.py +695 -0
  156. agentic_project_kit/gui_cockpit_common.py +172 -0
  157. agentic_project_kit/gui_cockpit_header.py +501 -0
  158. agentic_project_kit/gui_cockpit_sidebar.py +387 -0
  159. agentic_project_kit/gui_cockpit_task.py +748 -0
  160. agentic_project_kit/gui_command_projection.py +355 -0
  161. agentic_project_kit/gui_communication_modes.py +130 -0
  162. agentic_project_kit/gui_dry_run.py +219 -0
  163. agentic_project_kit/gui_gatekeeper_status.py +259 -0
  164. agentic_project_kit/gui_layout_plan.py +148 -0
  165. agentic_project_kit/gui_open_folder.py +18 -0
  166. agentic_project_kit/gui_output_status_panel.py +61 -0
  167. agentic_project_kit/gui_panel_state.py +47 -0
  168. agentic_project_kit/gui_presenter.py +62 -0
  169. agentic_project_kit/gui_readiness_gate.py +206 -0
  170. agentic_project_kit/gui_release_flow.py +59 -0
  171. agentic_project_kit/gui_task_editor.py +1077 -0
  172. agentic_project_kit/gui_tk_widgets.py +177 -0
  173. agentic_project_kit/gui_tkinter_renderer.py +87 -0
  174. agentic_project_kit/gui_tkinter_shell.py +1176 -0
  175. agentic_project_kit/gui_transfer_contract.py +28 -0
  176. agentic_project_kit/gui_viewmodel.py +698 -0
  177. agentic_project_kit/gui_window_guard.py +46 -0
  178. agentic_project_kit/handoff_freshness.py +390 -0
  179. agentic_project_kit/handoff_prompt.py +180 -0
  180. agentic_project_kit/handoff_state.py +254 -0
  181. agentic_project_kit/instruction_lint.py +455 -0
  182. agentic_project_kit/llm_context_carriers.py +66 -0
  183. agentic_project_kit/llm_execution_context.py +285 -0
  184. agentic_project_kit/local_command_stack.py +133 -0
  185. agentic_project_kit/local_feature_gate.py +82 -0
  186. agentic_project_kit/local_garbage_collector.py +269 -0
  187. agentic_project_kit/models.py +18 -0
  188. agentic_project_kit/mutation_lock_audit.py +421 -0
  189. agentic_project_kit/next_turn_evidence.py +253 -0
  190. agentic_project_kit/next_turn_merge_if_green.py +459 -0
  191. agentic_project_kit/next_turn_pr_status.py +255 -0
  192. agentic_project_kit/next_turn_result.py +183 -0
  193. agentic_project_kit/next_turn_runner.py +159 -0
  194. agentic_project_kit/next_turn_slot.py +107 -0
  195. agentic_project_kit/next_turn_status.py +274 -0
  196. agentic_project_kit/ns_legacy_reference_audit.py +258 -0
  197. agentic_project_kit/ns_slice_runner.py +16 -0
  198. agentic_project_kit/operational_handoff_projection.py +124 -0
  199. agentic_project_kit/output_contract.py +80 -0
  200. agentic_project_kit/output_repair.py +61 -0
  201. agentic_project_kit/pass_already_done.py +216 -0
  202. agentic_project_kit/patch_artifact_preflight.py +147 -0
  203. agentic_project_kit/patch_cycle_workflow.py +440 -0
  204. agentic_project_kit/patch_failure_discipline_audit.py +223 -0
  205. agentic_project_kit/patch_preflight.py +215 -0
  206. agentic_project_kit/path_literal_audit.py +612 -0
  207. agentic_project_kit/patterns.py +99 -0
  208. agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
  209. agentic_project_kit/portability_shell_gate.py +40 -0
  210. agentic_project_kit/post_merge_handoff_refresh.py +56 -0
  211. agentic_project_kit/post_release.py +252 -0
  212. agentic_project_kit/post_release_closeout.py +616 -0
  213. agentic_project_kit/pr_cleanup.py +172 -0
  214. agentic_project_kit/pr_closeout.py +50 -0
  215. agentic_project_kit/pr_closeout_complete.py +344 -0
  216. agentic_project_kit/pr_create_or_skip.py +71 -0
  217. agentic_project_kit/pr_hygiene.py +210 -0
  218. agentic_project_kit/program_redundancy_audit.py +251 -0
  219. agentic_project_kit/project_direction.py +840 -0
  220. agentic_project_kit/protected_change_planner.py +194 -0
  221. agentic_project_kit/release.py +401 -0
  222. agentic_project_kit/release_doi_safety.py +24 -0
  223. agentic_project_kit/release_gate_core.py +174 -0
  224. agentic_project_kit/release_metadata_authority_gate.py +328 -0
  225. agentic_project_kit/release_metadata_prep.py +43 -0
  226. agentic_project_kit/release_notes.py +823 -0
  227. agentic_project_kit/release_prep_core.py +204 -0
  228. agentic_project_kit/release_prepare.py +369 -0
  229. agentic_project_kit/release_process_guardrails.py +247 -0
  230. agentic_project_kit/release_publish_core.py +114 -0
  231. agentic_project_kit/release_publish_orchestration.py +469 -0
  232. agentic_project_kit/release_state.py +552 -0
  233. agentic_project_kit/release_verify_core.py +175 -0
  234. agentic_project_kit/remote_branch_hygiene.py +223 -0
  235. agentic_project_kit/remote_next.py +89 -0
  236. agentic_project_kit/remote_next_closeout.py +101 -0
  237. agentic_project_kit/remote_next_order_contract.py +391 -0
  238. agentic_project_kit/removed_ns_commands.py +458 -0
  239. agentic_project_kit/removed_source_audit.py +225 -0
  240. agentic_project_kit/rendering.py +13 -0
  241. agentic_project_kit/repair_report.py +79 -0
  242. agentic_project_kit/repo_identity.py +59 -0
  243. agentic_project_kit/result_report_classifier.py +57 -0
  244. agentic_project_kit/rule_ack.py +105 -0
  245. agentic_project_kit/rule_preservation.py +58 -0
  246. agentic_project_kit/rule_refresh.py +174 -0
  247. agentic_project_kit/rule_registry_registration.py +322 -0
  248. agentic_project_kit/rule_registry_report.py +206 -0
  249. agentic_project_kit/rule_registry_validator.py +704 -0
  250. agentic_project_kit/rule_snapshot.py +98 -0
  251. agentic_project_kit/rule_source_validator.py +133 -0
  252. agentic_project_kit/run_summary.py +68 -0
  253. agentic_project_kit/run_summary_renderer.py +238 -0
  254. agentic_project_kit/runtime_validator.py +68 -0
  255. agentic_project_kit/safe_file_replace.py +35 -0
  256. agentic_project_kit/safe_patch.py +47 -0
  257. agentic_project_kit/safe_push.py +218 -0
  258. agentic_project_kit/safe_remove_diagnostic.py +51 -0
  259. agentic_project_kit/scaffold.py +69 -0
  260. agentic_project_kit/site_claims.py +444 -0
  261. agentic_project_kit/site_generator.py +1140 -0
  262. agentic_project_kit/slice_gate.py +225 -0
  263. agentic_project_kit/slice_runner.py +75 -0
  264. agentic_project_kit/standard_gates_audit_suite.py +167 -0
  265. agentic_project_kit/state_freshness.py +135 -0
  266. agentic_project_kit/status_current_state_audit.py +762 -0
  267. agentic_project_kit/successor_handoff_package.py +1763 -0
  268. agentic_project_kit/templates.py +292 -0
  269. agentic_project_kit/terminal_block_guard.py +166 -0
  270. agentic_project_kit/terminal_logging.py +275 -0
  271. agentic_project_kit/todo.py +97 -0
  272. agentic_project_kit/transfer_closeout.py +136 -0
  273. agentic_project_kit/transfer_continue.py +215 -0
  274. agentic_project_kit/transfer_local_runner.py +101 -0
  275. agentic_project_kit/transfer_operation_monitor.py +236 -0
  276. agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
  277. agentic_project_kit/transfer_post_merge_settle.py +383 -0
  278. agentic_project_kit/transfer_pr_actions.py +164 -0
  279. agentic_project_kit/transfer_remote_next.py +959 -0
  280. agentic_project_kit/transfer_repo_actions.py +2253 -0
  281. agentic_project_kit/transfer_runner.py +347 -0
  282. agentic_project_kit/transfer_safety_context.py +291 -0
  283. agentic_project_kit/transfer_state.py +483 -0
  284. agentic_project_kit/transfer_uplink.py +471 -0
  285. agentic_project_kit/transfer_workflow_next.py +148 -0
  286. agentic_project_kit/typed_work_order_evidence.py +58 -0
  287. agentic_project_kit/typed_work_order_queue.py +166 -0
  288. agentic_project_kit/typed_work_order_runner.py +168 -0
  289. agentic_project_kit/work_cycle.py +317 -0
  290. agentic_project_kit/work_discard_changes.py +129 -0
  291. agentic_project_kit/work_order_patch.py +199 -0
  292. agentic_project_kit/work_order_runner.py +120 -0
  293. agentic_project_kit/work_order_uploader.py +220 -0
  294. agentic_project_kit/work_order_validator.py +185 -0
  295. agentic_project_kit/work_orders.py +246 -0
  296. agentic_project_kit/workflow_guard.py +262 -0
  297. agentic_project_kit/workflow_summary_runner.py +124 -0
  298. agentic_project_kit/workspace.py +665 -0
  299. agentic_project_kit/workspace_adopt.py +471 -0
  300. agentic_project_kit/workspace_dpa_intake.py +406 -0
  301. agentic_project_kit/workspace_init.py +487 -0
  302. agentic_project_kit/workspace_lock.py +129 -0
  303. agentic_project_kit/workspace_remove.py +381 -0
  304. agentic_project_kit/workspace_upgrade.py +324 -0
  305. agentic_project_kit/wrapper_live_status.py +104 -0
  306. agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
  307. agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
  308. agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
  309. agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
  310. 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()