delegate-agent-cli 0.27.0__tar.gz → 0.28.0__tar.gz

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 (192) hide show
  1. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/CHANGELOG.md +31 -1
  2. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/PKG-INFO +3 -1
  3. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/README.md +2 -0
  4. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/agent-setup.md +1 -1
  5. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/cli-reference.md +10 -3
  6. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/configuration.md +1 -1
  7. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/pyproject.toml +1 -1
  8. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/__init__.py +1 -1
  9. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/cli.py +52 -35
  10. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/cli_parser.py +6 -0
  11. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/command_help.py +17 -4
  12. delegate_agent_cli-0.28.0/src/delegate_agent/failover_state.py +201 -0
  13. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/inspection_commands.py +26 -1
  14. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/profiles.py +18 -1
  15. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/request_build.py +2 -0
  16. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/request_models.py +2 -0
  17. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/run_metadata.py +58 -0
  18. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/run_status.py +4 -0
  19. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/runner.py +186 -27
  20. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_execution.py +26 -9
  21. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent_cli.egg-info/PKG-INFO +3 -1
  22. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent_cli.egg-info/SOURCES.txt +2 -0
  23. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_command_help.py +16 -0
  24. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_execution_worktree_run.py +135 -0
  25. delegate_agent_cli-0.28.0/tests/test_failover_state.py +105 -0
  26. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_inspection_commands.py +91 -0
  27. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_profiles.py +177 -1
  28. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_runner_capture.py +363 -31
  29. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_utility_modules.py +2 -1
  30. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/CONTRIBUTING.md +0 -0
  31. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/LICENSE +0 -0
  32. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/MANIFEST.in +0 -0
  33. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/SECURITY.md +0 -0
  34. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/bin/delegate-profile-shim +0 -0
  35. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/bin/delegate.py +0 -0
  36. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/config.example.json +0 -0
  37. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/assets/delegate-agent-header.png +0 -0
  38. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/delegate-workflows.md +0 -0
  39. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/development.md +0 -0
  40. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/live-runtime.md +0 -0
  41. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/publishing-checklist.md +0 -0
  42. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/security-model.md +0 -0
  43. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/troubleshooting.md +0 -0
  44. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/docs/worktrees.md +0 -0
  45. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.claude.json +0 -0
  46. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.codex.json +0 -0
  47. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.cursor.json +0 -0
  48. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.droid.json +0 -0
  49. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.grok.json +0 -0
  50. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.judge.json +0 -0
  51. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/examples/task.kimi.json +0 -0
  52. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/setup.cfg +0 -0
  53. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/archived_logs.py +0 -0
  54. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/argv_builders.py +0 -0
  55. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/argv_utils.py +0 -0
  56. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/bundled_models.py +0 -0
  57. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/capability_commands.py +0 -0
  58. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/child_failures.py +0 -0
  59. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/command_errors.py +0 -0
  60. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/config.py +0 -0
  61. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/config_commands.py +0 -0
  62. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/constants.py +0 -0
  63. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/describe_payload.py +0 -0
  64. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/errors.py +0 -0
  65. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/git_utils.py +0 -0
  66. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/harness_discovery.py +0 -0
  67. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/harness_events.py +0 -0
  68. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/isolation.py +0 -0
  69. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/json_types.py +0 -0
  70. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/log_output.py +0 -0
  71. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/mail.py +0 -0
  72. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/mail_core.py +0 -0
  73. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/mail_push.py +0 -0
  74. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/model_discovery.py +0 -0
  75. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/personas.py +0 -0
  76. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/private_io.py +0 -0
  77. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/profile_commands.py +0 -0
  78. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/profile_guard.py +0 -0
  79. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/prompt_instructions.py +0 -0
  80. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/prompt_transport.py +0 -0
  81. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/reasoning.py +0 -0
  82. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/redaction.py +0 -0
  83. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/rendering.py +0 -0
  84. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/resume_command.py +0 -0
  85. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/retention.py +0 -0
  86. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/run_output_commands.py +0 -0
  87. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/run_registry.py +0 -0
  88. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/safe_workspace.py +0 -0
  89. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/seatbelt.py +0 -0
  90. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/setup_commands.py +0 -0
  91. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/snapshot_view.py +0 -0
  92. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/structured_output.py +0 -0
  93. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/wait_cancel_commands.py +0 -0
  94. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/workflows/__init__.py +0 -0
  95. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/workflows/commands.py +0 -0
  96. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/workflows/registry.py +0 -0
  97. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/workflows/runtime.py +0 -0
  98. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/workflows/schema.py +0 -0
  99. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/workflows/script.py +0 -0
  100. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_commands.py +0 -0
  101. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_gc.py +0 -0
  102. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_mgmt.py +0 -0
  103. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_records.py +0 -0
  104. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_remove.py +0 -0
  105. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/worktree_summary.py +0 -0
  106. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent/wsl.py +0 -0
  107. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent_cli.egg-info/dependency_links.txt +0 -0
  108. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent_cli.egg-info/entry_points.txt +0 -0
  109. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent_cli.egg-info/requires.txt +0 -0
  110. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/src/delegate_agent_cli.egg-info/top_level.txt +0 -0
  111. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_capability_commands.py +0 -0
  112. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_child_failures.py +0 -0
  113. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_codex_pure_sandbox.py +0 -0
  114. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_codex_thread_resilience.py +0 -0
  115. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_config_commands.py +0 -0
  116. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_delegate_help_cli.py +0 -0
  117. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_delegate_isolation.py +0 -0
  118. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_delegate_parser.py +0 -0
  119. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_delegate_validation.py +0 -0
  120. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_end_to_end_tracking.py +0 -0
  121. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_engine_argv.py +0 -0
  122. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_execution_argv_and_prompt.py +0 -0
  123. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_execution_dry_run.py +0 -0
  124. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_execution_worktree_failure_cleanup.py +0 -0
  125. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_execution_worktree_preflight.py +0 -0
  126. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_harness_discovery.py +0 -0
  127. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_harness_events.py +0 -0
  128. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_kimi_commands.py +0 -0
  129. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_bounds.py +0 -0
  130. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_contract.py +0 -0
  131. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_delivery.py +0 -0
  132. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_eligibility.py +0 -0
  133. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_framing.py +0 -0
  134. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_gating.py +0 -0
  135. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_identity.py +0 -0
  136. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_io.py +0 -0
  137. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_parser.py +0 -0
  138. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_prune.py +0 -0
  139. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_push.py +0 -0
  140. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_push_adapters.py +0 -0
  141. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_push_batch.py +0 -0
  142. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_push_cursor.py +0 -0
  143. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_push_gating.py +0 -0
  144. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_push_seams.py +0 -0
  145. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_reply.py +0 -0
  146. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_resolution.py +0 -0
  147. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_mail_rules.py +0 -0
  148. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_model_discovery.py +0 -0
  149. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_model_selection_wave1a.py +0 -0
  150. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_model_selection_wave1b.py +0 -0
  151. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_omp_read_only_behavior.py +0 -0
  152. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_packaging.py +0 -0
  153. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_capability.py +0 -0
  154. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_claude.py +0 -0
  155. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_contract.py +0 -0
  156. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_framer.py +0 -0
  157. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_opencode.py +0 -0
  158. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_parser.py +0 -0
  159. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_records.py +0 -0
  160. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_resolution.py +0 -0
  161. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_size_guard.py +0 -0
  162. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_validation.py +0 -0
  163. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_persona_workflow.py +0 -0
  164. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_pure_call.py +0 -0
  165. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_reasoning_capabilities.py +0 -0
  166. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_attachment.py +0 -0
  167. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_capture.py +0 -0
  168. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_inheritance.py +0 -0
  169. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_parser.py +0 -0
  170. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_projections.py +0 -0
  171. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_refusals.py +0 -0
  172. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_size_guard.py +0 -0
  173. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_stale.py +0 -0
  174. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_resume_trust.py +0 -0
  175. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_retention.py +0 -0
  176. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_run_registry.py +0 -0
  177. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_safe_workspace_isolation.py +0 -0
  178. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_setup_commands.py +0 -0
  179. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_slash_passthrough.py +0 -0
  180. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_snapshot_redaction.py +0 -0
  181. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_snapshot_rendering.py +0 -0
  182. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_snapshot_run_output.py +0 -0
  183. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_snapshot_view.py +0 -0
  184. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_structured_output.py +0 -0
  185. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_tracked_timeout.py +0 -0
  186. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_wait_cancel_commands.py +0 -0
  187. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_wave4_launch_features.py +0 -0
  188. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_workflow_commands.py +0 -0
  189. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_worktree_list_show.py +0 -0
  190. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_worktree_prune_gc.py +0 -0
  191. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_worktree_remove.py +0 -0
  192. {delegate_agent_cli-0.27.0 → delegate_agent_cli-0.28.0}/tests/test_wsl_guardrails.py +0 -0
@@ -5,7 +5,36 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.28.0] - 2026-08-05
9
+
10
+ ### Added
11
+
12
+ - Added `delegate runs --structural` and `delegate ps --structural` for
13
+ content-free local status collection, including lifecycle, model, group,
14
+ status, timestamp, and root initiator provenance.
15
+ - Tracked runs now retain a validated root initiator across nested Delegate
16
+ launches and persistent-worktree execution.
17
+
18
+ ### Changed
19
+
20
+ - Codex quota fallback is now explicitly configured with
21
+ `codex.fallbackProfile`, supports arbitrary profile definitions, and keeps
22
+ temporary blocks by resolved credential identity rather than profile alias.
23
+ Default work/personal credential homes continue to interoperate with legacy
24
+ profile launchers without allowing remapped aliases to share state.
25
+ - Codex-only failover no longer depends on private Claude shim names or hidden
26
+ runtime controls. Retry and persistent-worktree paths retain the account
27
+ that is actually serving the run.
28
+
29
+ ### Fixed
30
+
31
+ - Structural run listings now exclude raw terminal/provider events and apply
32
+ normal secret redaction to retained fields.
33
+ - Fallback failures persist the correct account block and provider-reported
34
+ reset time; a blocked fallback is skipped, and retries no longer return to a
35
+ blocked primary after a preflight swap.
36
+ - Failover tests isolate their state from the user's home directory, and
37
+ `delegate ps --help` now advertises its supported structural view.
9
38
 
10
39
  ## [0.27.0] - 2026-08-01
11
40
 
@@ -888,6 +917,7 @@ Usage-audit fix wave: 82 sessions and 1,241 delegate invocations from one week o
888
917
 
889
918
  - Releases before 0.1.3 predate this changelog.
890
919
 
920
+ [0.28.0]: https://github.com/treygoff24/delegate-agent/compare/v0.27.0...v0.28.0
891
921
  [0.27.0]: https://github.com/treygoff24/delegate-agent/compare/v0.26.0...v0.27.0
892
922
  [0.26.0]: https://github.com/treygoff24/delegate-agent/compare/v0.25.0...v0.26.0
893
923
  [0.25.0]: https://github.com/treygoff24/delegate-agent/compare/v0.24.0...v0.25.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: delegate-agent-cli
3
- Version: 0.27.0
3
+ Version: 0.28.0
4
4
  Summary: A tiny CLI for delegating bounded agent tasks across coding-agent runtimes.
5
5
  Author: Trey Goff
6
6
  License-Expression: MIT
@@ -167,6 +167,8 @@ delegate --json capabilities refresh # refresh the active profile's discovery
167
167
 
168
168
  Discover commands as you go: `delegate <command> --help` prints focused help for any command path, and `delegate --json <command> --help` returns an agent-friendly spec of its usage, arguments, and options. For launch and `dry-run` commands, `--json` and `--isolation` may also appear before inline prompt text begins, for example `delegate codex work --prompt-file task.md --json`. `delegate --json describe` includes a `commands` catalog of the whole surface. Cached `models` and `capabilities` reads launch no child process. `models <engine> --live` performs a one-off probe without updating the cache; rerun `delegate setup` or `delegate capabilities refresh` when ordinary launches should consume newly discovered models or effort levels.
169
169
 
170
+ Codex tracked runs can opt into a one-run quota fallback with `codex.fallbackProfile`; hashed credential namespace is canonical, while default work/personal credential homes also mirror compatible legacy alias keys so existing launchers share blocks. Remapped aliases remain isolated. See [Configuration](docs/configuration.md).
171
+
170
172
  From this development checkout, use `python3 bin/delegate.py ...` instead of an installed `delegate` shim.
171
173
 
172
174
  ### WSL setup notes
@@ -134,6 +134,8 @@ delegate --json capabilities refresh # refresh the active profile's discovery
134
134
 
135
135
  Discover commands as you go: `delegate <command> --help` prints focused help for any command path, and `delegate --json <command> --help` returns an agent-friendly spec of its usage, arguments, and options. For launch and `dry-run` commands, `--json` and `--isolation` may also appear before inline prompt text begins, for example `delegate codex work --prompt-file task.md --json`. `delegate --json describe` includes a `commands` catalog of the whole surface. Cached `models` and `capabilities` reads launch no child process. `models <engine> --live` performs a one-off probe without updating the cache; rerun `delegate setup` or `delegate capabilities refresh` when ordinary launches should consume newly discovered models or effort levels.
136
136
 
137
+ Codex tracked runs can opt into a one-run quota fallback with `codex.fallbackProfile`; hashed credential namespace is canonical, while default work/personal credential homes also mirror compatible legacy alias keys so existing launchers share blocks. Remapped aliases remain isolated. See [Configuration](docs/configuration.md).
138
+
137
139
  From this development checkout, use `python3 bin/delegate.py ...` instead of an installed `delegate` shim.
138
140
 
139
141
  ### WSL setup notes
@@ -7,7 +7,7 @@ This guide covers both human setup and non-interactive setup for agents or CI jo
7
7
  1. Install Delegate from the source repository:
8
8
 
9
9
  ```bash
10
- python3 -m pip install "delegate-agent @ git+https://github.com/treygoff24/delegate-agent.git"
10
+ python3 -m pip install "delegate-agent-cli @ git+https://github.com/treygoff24/delegate-agent.git"
11
11
  ```
12
12
 
13
13
  For a development checkout, prefer:
@@ -1008,7 +1008,7 @@ repo copy or persistent worktree itself.
1008
1008
  Tracked runs return bounded parent-facing output and store local metadata under `.delegate/` in the source workspace.
1009
1009
 
1010
1010
  ```bash
1011
- delegate runs [--active|--running|--stale|--recent] [--harness HARNESS] [--group NAME] [--limit N]
1011
+ delegate runs [--active|--running|--stale|--recent] [--harness HARNESS] [--group NAME] [--limit N] [--structural]
1012
1012
  delegate runs prune [--older-than DAYS] [--dry-run]
1013
1013
  delegate ps [--harness HARNESS] [--group NAME] [--limit N]
1014
1014
  delegate snapshot [--latest HARNESS] [--no-redact] <handle>
@@ -1055,8 +1055,10 @@ creating a Run or writing a prompt record.
1055
1055
  | Include dirty | `includeDirty` | `--include-dirty` | Source and explicit values are creation-only and are dropped for ordinary resume with a note. | Retained only as a drop decision; on a persistent/attached source, explicit `--include-dirty` is rejected because attachment does not create or sync a worktree. |
1056
1056
 
1057
1057
  `delegate runs` defaults to recent runs. `--active` preserves the legacy active view and includes both live `running` runs and `stale` runs. Use `--running` for only live tracked processes and `--stale` for runs recorded as running whose PID is missing or dead. `--active`, `--running`, `--stale`, and `--recent` are mutually exclusive. `--group NAME` filters by launch group and the runs table shows a `group` column when any visible run has one.
1058
+ `--structural` omits content-bearing run fields and emits only identity, lifecycle, model,
1059
+ timestamp, group/mode, and `initiatorRoot` metadata. It is intended for local status collectors.
1058
1060
  `delegate ps` is the shorter first-class form of `delegate runs --active` and
1059
- accepts the same harness, group, and limit selectors.
1061
+ accepts the same harness, group, limit, and structural selectors.
1060
1062
 
1061
1063
  `delegate runs prune` removes old terminal Run records from the workspace Registry so they stop accumulating forever. Only runs whose effective status is terminal (`succeeded`, `failed`, `cancelled`, or `stale` from a dead child) and whose last Registry activity is older than the threshold (default 30 days; override with `--older-than DAYS`) are eligible. Effectively running runs are always skipped, and persistent-worktree runs are skipped unless their worktree is recorded as removed or missing — pruning the record of a live worktree would orphan it from `worktree list`/`show`/`remove`. Pruning deletes the per-Run directory (Snapshot, Manifest, logs, events, Completion Report) and the retained raw-log archive; worktree paths on disk are never touched. `--dry-run` reports what would be removed without changing anything. JSON output uses schema `delegate.runs-prune.v1` with `planned`, `removed`, `skipped` (each entry carries a `reason`), and `errors` sections.
1062
1064
 
@@ -1114,7 +1116,12 @@ Persistent worktree completions also include `branch`, `worktree`, a
1114
1116
  state, changed file count, diff stat, and commits created by the child. When a
1115
1117
  Codex usage-limit fallback fires, the completion payload also includes
1116
1118
  `codexAuthFallback` metadata (reason, the primary and fallback profile names,
1117
- both exit codes, and a redacted primary stderr tail).
1119
+ both exit codes, and a redacted primary stderr tail). The fallback is Codex-only
1120
+ and is enabled only by `codex.fallbackProfile`; Delegate remembers temporary
1121
+ blocks by hashed credential namespace (`CODEX_HOME/auth.json` plus
1122
+ `codex.profile`) as canonical state. Default work/personal credential homes
1123
+ also mirror compatible legacy alias keys so existing launchers share blocks;
1124
+ remapped aliases remain isolated.
1118
1125
 
1119
1126
  Snapshot JSON uses schema `delegate.snapshot.v1` and includes fields such as `alias`, `runId`, `harness`, `status`, `rawStatus`, `effectiveStatus`, `staleReason`, `nextActions`, `cwd`, `executionCwd`, `workspaceRoot`, `assistantText`, `recentEvents`, `warnings`, `exitCode`, reasoning metadata, terminal metadata, and isolation/worktree metadata when applicable. `workspaceRoot` is also exported to the child as `WORKSPACE_ROOT`, so commands can anchor workspace-relative paths after changing directories. Inspection commands do not rewrite a stale run's recorded state; they expose the raw recorded status plus the effective status computed from the current PID check. Run-output and worktree show output include `requestedHandle`, `resolvedHandle`, and `resolutionKind` (`literal`, `latest`, or `latest_model`) when a handle resolves indirectly. For bare harness handles, snapshot, run-output, and wait also report `resolvedRunId`, `resolvedAlias`, `resolvedWorkspace`, `resolvedAge`, and `resolvedAgeSeconds`. Resolutions older than 24 hours add a `bare_handle_stale` warning suggesting `--cwd` or an explicit handle.
1120
1127
 
@@ -336,7 +336,7 @@ Controls local run recording.
336
336
  - `models`: optional map of local aliases to Codex model IDs for `--model` / JSON `model`. Alias keys must not collide with mode names, equal the engine's own name, or start with `-`.
337
337
  - `defaultReasoningEffort`: optional non-empty effort string. When a Codex model resolves (run input or `codex.defaultModel`) and supports the level, Delegate emits a Codex config override; otherwise the run proceeds without reasoning effort and records a warning. An explicit `--reasoning-effort` flag fails closed for unsupported levels, but can target the Codex harness default model when no model is configured.
338
338
  - `profile`: optional Codex CLI config overlay name. It is config-only; JSON run input cannot set it.
339
- - `fallbackProfile`: optional top-level `profiles.definitions` name for one quota-limit retry on tracked Codex runs. The fallback profile must define `env.CODEX_HOME`.
339
+ - `fallbackProfile`: optional top-level `profiles.definitions` name for Codex-only quota fallback. The fallback profile must define `env.CODEX_HOME`. Tracked Codex runs persist temporary usage-limit blocks under `~/.ai-profiles/runtime/failover/`; hashed credential namespace (`CODEX_HOME/auth.json` plus `codex.profile`) is canonical, while default work/personal credential homes also mirror compatible legacy alias keys so existing launchers share blocks. Remapped aliases remain isolated. If both the primary and fallback credential namespaces are blocked, Delegate keeps the primary attempt and does not launch the known-blocked fallback.
340
340
  - `workSandbox`: `read-only`, `workspace-write`, or `danger-full-access` for Codex work mode when full bypass is not enabled.
341
341
  - `ephemeral`: include Codex `--ephemeral` in JSON-streaming runs.
342
342
  - `ignoreUserConfig`: include Codex `--ignore-user-config`.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "delegate-agent-cli"
7
- version = "0.27.0"
7
+ version = "0.28.0"
8
8
  description = "A tiny CLI for delegating bounded agent tasks across coding-agent runtimes."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,5 +1,5 @@
1
1
  """Delegate Agent CLI package."""
2
2
 
3
- VERSION = "0.27.0"
3
+ VERSION = "0.28.0"
4
4
 
5
5
  __all__ = ["VERSION"]
@@ -635,6 +635,8 @@ def make_run_context(
635
635
  ),
636
636
  auth_profile=request.auth_profile,
637
637
  fallback_auth_profile=request.fallback_auth_profile,
638
+ codex_failover_identity=request.codex_failover_identity,
639
+ codex_fallback_failover_identity=request.codex_fallback_failover_identity,
638
640
  include_dirty=request.include_dirty,
639
641
  mail_push=request.mail_push,
640
642
  group=request.group,
@@ -675,6 +677,12 @@ def _set_child_root_env(request: Request, source_workspace: ResolvedWorkspace) -
675
677
  request.env_overrides = env
676
678
 
677
679
 
680
+ def _apply_initiator_root(request: Request) -> str | None:
681
+ initiator_root, env = run_metadata.apply_initiator_root_env(request.env_overrides)
682
+ request.env_overrides = env
683
+ return initiator_root
684
+
685
+
678
686
  def _artifact_files(root: Path) -> list[str]:
679
687
  return sorted(str(item) for item in root.rglob("*") if item.is_file() or item.is_symlink())
680
688
 
@@ -786,22 +794,26 @@ def _execute_attached_worktree(
786
794
  if request.mode == MODE_WORK and delegate_config.mail_enabled(config):
787
795
  mail.prepare_mail_storage(registry_root)
788
796
  mail.sanitize_inherited_mail_identity(request.env_overrides)
797
+ metadata = {
798
+ "mode": request.mode,
799
+ "model": request.model,
800
+ "modelAlias": request.model_alias,
801
+ "modelResolved": request.model,
802
+ "group": request.group,
803
+ "workflowAgentKey": request.workflow_agent_key,
804
+ "cwd": source_workspace.path,
805
+ "worktreeAttachment": {
806
+ **(iso.attachment or {}),
807
+ "startHeadOid": start_head_oid,
808
+ },
809
+ }
810
+ run_metadata.add_initiator_metadata(
811
+ metadata, run_metadata.resolve_initiator_root(request.env_overrides or {})
812
+ )
789
813
  run_id, alias = run_registry.register_run(
790
814
  registry_root,
791
815
  harness=request.engine,
792
- metadata={
793
- "mode": request.mode,
794
- "model": request.model,
795
- "modelAlias": request.model_alias,
796
- "modelResolved": request.model,
797
- "group": request.group,
798
- "workflowAgentKey": request.workflow_agent_key,
799
- "cwd": source_workspace.path,
800
- "worktreeAttachment": {
801
- **(iso.attachment or {}),
802
- "startHeadOid": start_head_oid,
803
- },
804
- },
816
+ metadata=metadata,
805
817
  )
806
818
  child_env = request.env_overrides or {}
807
819
  if request.mode == MODE_WORK:
@@ -986,6 +998,7 @@ def execute_request(
986
998
  stderr: TextIO,
987
999
  ) -> tuple[int, JsonObject | None]:
988
1000
  _set_child_root_env(request, source_workspace)
1001
+ initiator_root = _apply_initiator_root(request)
989
1002
  profiles.strip_mail_identity(request.env_overrides)
990
1003
  if request.mode == MODE_CALL:
991
1004
  call_response: tuple[int, JsonObject | None] | None = None
@@ -1013,18 +1026,20 @@ def execute_request(
1013
1026
  workspace_kind=source_workspace.kind,
1014
1027
  )
1015
1028
  maybe_run_retention_pass(registry_root, config)
1029
+ metadata = {
1030
+ "mode": request.mode,
1031
+ "model": request.model,
1032
+ "modelAlias": request.model_alias,
1033
+ "modelResolved": request.model,
1034
+ "group": request.group,
1035
+ "workflowAgentKey": request.workflow_agent_key,
1036
+ "cwd": source_workspace.path,
1037
+ }
1038
+ run_metadata.add_initiator_metadata(metadata, initiator_root)
1016
1039
  run_id, alias = run_registry.register_run(
1017
1040
  registry_root,
1018
1041
  harness=request.engine,
1019
- metadata={
1020
- "mode": request.mode,
1021
- "model": request.model,
1022
- "modelAlias": request.model_alias,
1023
- "modelResolved": request.model,
1024
- "group": request.group,
1025
- "workflowAgentKey": request.workflow_agent_key,
1026
- "cwd": source_workspace.path,
1027
- },
1042
+ metadata=metadata,
1028
1043
  )
1029
1044
  artifact_id = run_id
1030
1045
  ctx_runner = make_run_context(
@@ -1282,22 +1297,24 @@ def execute_request(
1282
1297
  if isolated_request.mode == MODE_WORK and delegate_config.mail_enabled(config):
1283
1298
  mail.prepare_mail_storage(registry_root)
1284
1299
  mail.sanitize_inherited_mail_identity(isolated_request.env_overrides)
1300
+ metadata = {
1301
+ "mode": isolated_request.mode,
1302
+ "model": isolated_request.model,
1303
+ "modelAlias": isolated_request.model_alias,
1304
+ "modelResolved": isolated_request.model,
1305
+ "group": isolated_request.group,
1306
+ "workflowAgentKey": isolated_request.workflow_agent_key,
1307
+ "cwd": (
1308
+ isolated_request.isolation_context.source_workspace
1309
+ if isolated_request.isolation_context is not None
1310
+ else source_workspace.path
1311
+ ),
1312
+ }
1313
+ run_metadata.add_initiator_metadata(metadata, initiator_root)
1285
1314
  run_id, alias = run_registry.register_run(
1286
1315
  registry_root,
1287
1316
  harness=isolated_request.engine,
1288
- metadata={
1289
- "mode": isolated_request.mode,
1290
- "model": isolated_request.model,
1291
- "modelAlias": isolated_request.model_alias,
1292
- "modelResolved": isolated_request.model,
1293
- "group": isolated_request.group,
1294
- "workflowAgentKey": isolated_request.workflow_agent_key,
1295
- "cwd": (
1296
- isolated_request.isolation_context.source_workspace
1297
- if isolated_request.isolation_context is not None
1298
- else source_workspace.path
1299
- ),
1300
- },
1317
+ metadata=metadata,
1301
1318
  )
1302
1319
  child_env = isolated_request.env_overrides or {}
1303
1320
  if isolated_request.mode == MODE_WORK:
@@ -1875,6 +1875,7 @@ def parse_runs(
1875
1875
  recent = False
1876
1876
  running = False
1877
1877
  stale = False
1878
+ structural = False
1878
1879
  harness: str | None = None
1879
1880
  group: str | None = None
1880
1881
  limit: int | None = None
@@ -1897,6 +1898,10 @@ def parse_runs(
1897
1898
  recent = True
1898
1899
  i += 1
1899
1900
  continue
1901
+ if token == "--structural":
1902
+ structural = True
1903
+ i += 1
1904
+ continue
1900
1905
  if token == "--harness":
1901
1906
  if i + 1 >= len(rest):
1902
1907
  raise DelegateError(
@@ -1953,6 +1958,7 @@ def parse_runs(
1953
1958
  harness=harness,
1954
1959
  group=group,
1955
1960
  limit=limit,
1961
+ structural=structural,
1956
1962
  json_mode=json_mode,
1957
1963
  ),
1958
1964
  )
@@ -875,7 +875,7 @@ COMMAND_SPECS: dict[str, CommandSpec] = {
875
875
  summary="List tracked runs, optionally filtered by activity, recency, or harness.",
876
876
  usage=(
877
877
  "delegate [--json] runs [--active|--running|--stale|--recent] "
878
- "[--harness HARNESS] [--group NAME] [--limit N]",
878
+ "[--harness HARNESS] [--group NAME] [--limit N] [--structural]",
879
879
  ),
880
880
  options=(
881
881
  OptionSpec(
@@ -893,6 +893,11 @@ COMMAND_SPECS: dict[str, CommandSpec] = {
893
893
  ),
894
894
  OptionSpec("--limit", "N", "Cap the number of runs listed (positive integer)."),
895
895
  OptionSpec("--group", "NAME", "Filter by launch group."),
896
+ OptionSpec(
897
+ "--structural",
898
+ None,
899
+ "Emit only lifecycle, model, provenance, and identity metadata; omit run content.",
900
+ ),
896
901
  ),
897
902
  examples=(
898
903
  "delegate runs --active",
@@ -900,6 +905,7 @@ COMMAND_SPECS: dict[str, CommandSpec] = {
900
905
  "delegate runs --stale",
901
906
  "delegate runs --harness cursor --limit 5",
902
907
  "delegate runs --group wave4",
908
+ "delegate --json runs --recent --structural",
903
909
  ),
904
910
  notes=("--active, --running, --stale, and --recent are mutually exclusive.",),
905
911
  see_also=("ps", "runs prune", "snapshot", "run-output"),
@@ -1101,13 +1107,20 @@ COMMAND_SPECS: dict[str, CommandSpec] = {
1101
1107
  "ps": CommandSpec(
1102
1108
  name="ps",
1103
1109
  summary="List active tracked runs (alias for runs --active).",
1104
- usage=("delegate [--json] ps [--harness HARNESS] [--group NAME] [--limit N]",),
1110
+ usage=(
1111
+ "delegate [--json] ps [--harness HARNESS] [--group NAME] [--limit N] [--structural]",
1112
+ ),
1105
1113
  options=(
1106
1114
  OptionSpec("--harness", "HARNESS", f"Filter by harness: {ENGINES_PROSE}."),
1107
1115
  OptionSpec("--limit", "N", "Cap the number of runs listed (positive integer)."),
1108
1116
  OptionSpec("--group", "NAME", "Filter by launch group."),
1117
+ OptionSpec(
1118
+ "--structural",
1119
+ None,
1120
+ "Emit only lifecycle, model, provenance, and identity metadata; omit run content.",
1121
+ ),
1109
1122
  ),
1110
- examples=("delegate ps", "delegate ps --harness codex"),
1123
+ examples=("delegate ps", "delegate ps --harness codex", "delegate --json ps --structural"),
1111
1124
  notes=("Equivalent to delegate runs --active.",),
1112
1125
  see_also=("runs", "snapshot", "run-output"),
1113
1126
  unsupported_global_options=("--auth-profile",),
@@ -2101,7 +2114,7 @@ def render_overview_text() -> str:
2101
2114
  "[--active|--running|--stale|--recent] [--harness HARNESS] [--limit N]",
2102
2115
  "delegate [--cwd PATH] [--json] runs prune [--older-than DAYS] [--dry-run]",
2103
2116
  "delegate [--cwd PATH] [--json] mail {send,inbox,read,status,watch,prune} ...",
2104
- "delegate [--cwd PATH] [--json] ps [--harness HARNESS] [--group NAME] [--limit N]",
2117
+ "delegate [--cwd PATH] [--json] ps [--harness HARNESS] [--group NAME] [--limit N] [--structural]",
2105
2118
  "delegate [--cwd PATH] [--json] run-output <handle> "
2106
2119
  "[--completion-report] [--stdout] [--stderr] [--tail N] [--max-chars N] "
2107
2120
  "[--raw] [--no-redact]",
@@ -0,0 +1,201 @@
1
+ """Persistent usage-limit state shared by Delegate and profile launchers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import datetime
7
+ import hashlib
8
+ import os
9
+ import re
10
+ import time
11
+ from collections.abc import Generator
12
+ from pathlib import Path
13
+
14
+ _VALID_TOOLS = frozenset({"codex"})
15
+ _LEGACY_PROFILES = frozenset({"work", "personal"})
16
+ _DEFAULT_COOLDOWN_SEC = 1800
17
+ _LOCK_WAIT_SEC = 5.0
18
+ _LOCK_STALE_SEC = 30.0
19
+
20
+
21
+ def _root() -> Path:
22
+ return Path.home() / ".ai-profiles" / "runtime" / "failover"
23
+
24
+
25
+ def _state_slug(tool: str, identity: str) -> str:
26
+ digest = hashlib.sha256(f"{tool}\0{identity}".encode()).hexdigest()
27
+ return digest[:32]
28
+
29
+
30
+ def _state_file(tool: str, identity: str) -> Path:
31
+ return _root() / f"{tool}-{_state_slug(tool, identity)}.blocked-until"
32
+
33
+
34
+ @contextlib.contextmanager
35
+ def _lock_path(lock: Path) -> Generator[bool, None, None]:
36
+ deadline = time.monotonic() + _LOCK_WAIT_SEC
37
+ acquired = False
38
+ while not acquired:
39
+ try:
40
+ lock.mkdir()
41
+ acquired = True
42
+ except FileExistsError:
43
+ try:
44
+ if time.time() - lock.stat().st_mtime > _LOCK_STALE_SEC:
45
+ lock.rmdir()
46
+ continue
47
+ except OSError:
48
+ pass
49
+ if time.monotonic() >= deadline:
50
+ break
51
+ time.sleep(0.1)
52
+ except OSError:
53
+ break
54
+ try:
55
+ yield acquired
56
+ finally:
57
+ if acquired:
58
+ with contextlib.suppress(OSError):
59
+ lock.rmdir()
60
+
61
+
62
+ def _lock(tool: str, identity: str) -> Generator[bool, None, None]:
63
+ return _lock_path(_root() / f"{tool}-{_state_slug(tool, identity)}.lock")
64
+
65
+
66
+ def _valid(tool: str, identity: str | None) -> bool:
67
+ return tool in _VALID_TOOLS and isinstance(identity, str) and bool(identity)
68
+
69
+
70
+ def _legacy_identity(profile_alias: str) -> str:
71
+ homes = {
72
+ "work": Path.home() / ".codex",
73
+ "personal": Path.home() / ".ai-profiles" / "runtime" / "codex" / "personal",
74
+ }
75
+ return f"auth={(homes[profile_alias] / 'auth.json').resolve(strict=False)}\0profile="
76
+
77
+
78
+ def _legacy_profile(tool: str, identity: str, profile_alias: str | None) -> str | None:
79
+ if tool != "codex" or profile_alias not in _LEGACY_PROFILES:
80
+ return None
81
+ if identity != _legacy_identity(profile_alias):
82
+ return None
83
+ return profile_alias
84
+
85
+
86
+ def _legacy_state_file(tool: str, profile_alias: str) -> Path:
87
+ return _root() / f"{tool}-{profile_alias}.blocked-until"
88
+
89
+
90
+ def _legacy_lock(tool: str, profile_alias: str) -> Generator[bool, None, None]:
91
+ return _lock_path(_root() / f"{tool}-{profile_alias}.lock")
92
+
93
+
94
+ def _read_block(state: Path, lock: Generator[bool, None, None]) -> int | None:
95
+ with lock as acquired:
96
+ if not acquired or not state.exists():
97
+ return None
98
+ raw = state.read_text().strip()
99
+ if not raw.isdigit() or int(raw) <= int(time.time()):
100
+ state.unlink(missing_ok=True)
101
+ return None
102
+ return int(raw)
103
+
104
+
105
+ def _write_state(state: Path, lock: Generator[bool, None, None], proposed: int) -> None:
106
+ with lock as acquired:
107
+ if not acquired:
108
+ return
109
+ current = state.read_text().strip() if state.exists() else ""
110
+ expiry = max(proposed, int(current) if current.isdigit() else 0)
111
+ temp = state.with_name(f"{state.name}.tmp.{os.getpid()}")
112
+ temp.write_text(f"{expiry}\n")
113
+ os.chmod(temp, 0o600)
114
+ os.replace(temp, state)
115
+
116
+
117
+ def check_blocked(
118
+ tool: str, identity: str | None, *, profile_alias: str | None = None
119
+ ) -> tuple[bool, int | None]:
120
+ if not _valid(tool, identity):
121
+ return False, None
122
+ assert identity is not None
123
+ try:
124
+ _root().mkdir(parents=True, exist_ok=True, mode=0o700)
125
+ expiries = [_read_block(_state_file(tool, identity), _lock(tool, identity))]
126
+ legacy = _legacy_profile(tool, identity, profile_alias)
127
+ if legacy is not None:
128
+ expiries.append(
129
+ _read_block(_legacy_state_file(tool, legacy), _legacy_lock(tool, legacy))
130
+ )
131
+ active = [expiry for expiry in expiries if expiry is not None]
132
+ if not active:
133
+ return False, None
134
+ return True, max(active)
135
+ except (OSError, ValueError):
136
+ return False, None
137
+
138
+
139
+ def write_block(
140
+ tool: str,
141
+ identity: str | None,
142
+ reset_epoch: int | None = None,
143
+ *,
144
+ profile_alias: str | None = None,
145
+ ) -> None:
146
+ if not _valid(tool, identity):
147
+ return
148
+ assert identity is not None
149
+ try:
150
+ root = _root()
151
+ root.mkdir(parents=True, exist_ok=True, mode=0o700)
152
+ os.chmod(root, 0o700)
153
+ proposed = reset_epoch or int(time.time()) + _DEFAULT_COOLDOWN_SEC
154
+ _write_state(_state_file(tool, identity), _lock(tool, identity), proposed)
155
+ legacy = _legacy_profile(tool, identity, profile_alias)
156
+ if legacy is not None:
157
+ _write_state(_legacy_state_file(tool, legacy), _legacy_lock(tool, legacy), proposed)
158
+ except (OSError, ValueError):
159
+ pass
160
+
161
+
162
+ def clear_block(tool: str, identity: str | None, *, profile_alias: str | None = None) -> None:
163
+ if not _valid(tool, identity):
164
+ return
165
+ assert identity is not None
166
+ try:
167
+ with _lock(tool, identity) as acquired:
168
+ if acquired:
169
+ _state_file(tool, identity).unlink(missing_ok=True)
170
+ legacy = _legacy_profile(tool, identity, profile_alias)
171
+ if legacy is not None:
172
+ with _legacy_lock(tool, legacy) as acquired:
173
+ if acquired:
174
+ _legacy_state_file(tool, legacy).unlink(missing_ok=True)
175
+ except OSError:
176
+ pass
177
+
178
+
179
+ def parse_reset_epoch(text: str) -> int | None:
180
+ match = re.search(r"try again at (\d{1,2}):(\d{2})\s*(AM|PM)", text, re.IGNORECASE)
181
+ if not match:
182
+ return None
183
+ hour, minute = int(match.group(1)), int(match.group(2))
184
+ if not 1 <= hour <= 12 or not 0 <= minute <= 59:
185
+ return None
186
+ if match.group(3).upper() == "PM" and hour != 12:
187
+ hour += 12
188
+ elif match.group(3).upper() == "AM" and hour == 12:
189
+ hour = 0
190
+ now = datetime.datetime.now()
191
+ reset = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
192
+ if reset <= now:
193
+ reset += datetime.timedelta(days=1)
194
+ return int(reset.timestamp())
195
+
196
+
197
+ def epoch_to_human(epoch: int) -> str:
198
+ try:
199
+ return datetime.datetime.fromtimestamp(epoch).strftime("%H:%M")
200
+ except (OSError, OverflowError, ValueError):
201
+ return "?:??"
@@ -8,6 +8,22 @@ from delegate_agent import command_errors, redaction, run_registry, snapshot_vie
8
8
  from delegate_agent import rendering as delegate_rendering
9
9
  from delegate_agent.json_types import JsonObject
10
10
 
11
+ STRUCTURAL_RUN_KEYS = (
12
+ "runId",
13
+ "alias",
14
+ "harness",
15
+ "group",
16
+ "mode",
17
+ "modelAlias",
18
+ "modelResolved",
19
+ "rawStatus",
20
+ "effectiveStatus",
21
+ "status",
22
+ "terminalStatus",
23
+ "activityAt",
24
+ "initiatorRoot",
25
+ )
26
+
11
27
 
12
28
  @dataclass(frozen=True)
13
29
  class SnapshotCommand:
@@ -28,6 +44,7 @@ class RunsCommand:
28
44
  limit: int | None = None
29
45
  older_than_days: int | None = None
30
46
  dry_run: bool = False
47
+ structural: bool = False
31
48
  json_mode: bool = False
32
49
 
33
50
 
@@ -121,7 +138,15 @@ def emit_runs(command: RunsCommand, *, workspace_path: str, stdout: TextIO) -> i
121
138
  group=command.group,
122
139
  limit=limit,
123
140
  )
124
- summaries = [redaction.redact_value(summary) for summary in summaries]
141
+ if command.structural:
142
+ summaries = [
143
+ redaction.redact_value(
144
+ {key: summary[key] for key in STRUCTURAL_RUN_KEYS if key in summary}
145
+ )
146
+ for summary in summaries
147
+ ]
148
+ else:
149
+ summaries = [redaction.redact_value(summary) for summary in summaries]
125
150
  if command.json_mode:
126
151
  delegate_rendering.print_json(
127
152
  delegate_rendering.runs_json_payload(summaries, limit=limit, mode=mode),