engineering-platform 2.3.79__tar.gz → 2.3.81__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 (168) hide show
  1. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/PKG-INFO +1 -1
  2. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/pyproject.toml +2 -1
  3. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +1 -1
  4. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
  5. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/dashboard.css +3 -1
  6. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/dashboard.js +167 -127
  7. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/dashboard_locales.mjs +5 -0
  8. engineering_platform-2.3.81/src/engineering_platform/central_operational_reset.py +2144 -0
  9. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_reporting.py +210 -63
  10. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_timing.py +105 -31
  11. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/parity_lifecycle_dispatcher.py +18 -4
  12. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/platform_version.py +1 -1
  13. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_usage.py +316 -63
  14. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/server.py +490 -51
  15. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/storage.py +258 -50
  16. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/submission_service.py +63 -1
  17. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/telemetry_contract.py +92 -42
  18. engineering_platform-2.3.81/src/engineering_platform/telemetry_export.py +495 -0
  19. engineering_platform-2.3.81/src/engineering_platform/telemetry_metrics.py +194 -0
  20. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/templates/workspace-config.json +1 -1
  21. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform.egg-info/PKG-INFO +1 -1
  22. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform.egg-info/SOURCES.txt +3 -0
  23. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform.egg-info/entry_points.txt +1 -0
  24. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/README.md +0 -0
  25. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/setup.cfg +0 -0
  26. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/__init__.py +0 -0
  27. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/__main__.py +0 -0
  28. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/agent_state.py +0 -0
  29. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/agent_trust.py +0 -0
  30. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
  31. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/dashboard_translation.mjs +0 -0
  32. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  33. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  34. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
  35. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
  36. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  37. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
  38. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/capability_preflight.py +0 -0
  39. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/capability_review.py +0 -0
  40. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/central_data_transfer.py +0 -0
  41. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/central_database.py +0 -0
  42. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/central_store_migration.py +0 -0
  43. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/codex_capacity.py +0 -0
  44. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/codex_chat.py +0 -0
  45. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/codex_observability.py +0 -0
  46. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/component_lock.py +0 -0
  47. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/component_logging.py +0 -0
  48. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/console_presentation.py +0 -0
  49. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/console_route_ownership.py +0 -0
  50. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/contracts/__init__.py +0 -0
  51. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/contracts/ep_consumer.py +0 -0
  52. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/contracts/models.py +0 -0
  53. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/contracts/projection.py +0 -0
  54. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/dashboard_browser_validation.py +0 -0
  55. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/dashboard_state.py +0 -0
  56. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/dashboard_supervisor.swift +0 -0
  57. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/dashboard_translation.py +0 -0
  58. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/dependabot_producer.py +0 -0
  59. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/development_profile.py +0 -0
  60. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/drift_diagnostics.py +0 -0
  61. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/emergency_recovery.py +0 -0
  62. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/engineering_memory.py +0 -0
  63. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/ep_consumer_credentials.py +0 -0
  64. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/evidence_projection.py +0 -0
  65. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_activity.py +0 -0
  66. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_context.py +0 -0
  67. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_errors.py +0 -0
  68. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_evidence.py +0 -0
  69. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_executor.py +0 -0
  70. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_finalization.py +0 -0
  71. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_host.py +0 -0
  72. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_host_evidence.py +0 -0
  73. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_lease.py +0 -0
  74. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_lifecycle.py +0 -0
  75. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_models.py +0 -0
  76. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_readiness.py +0 -0
  77. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_repository.py +0 -0
  78. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_timeout_policy.py +0 -0
  79. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/execution_transaction.py +0 -0
  80. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/external_producer_binding.py +0 -0
  81. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/file_inbox.py +0 -0
  82. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/forensic_attribution.py +0 -0
  83. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/forensic_attribution_v2.py +0 -0
  84. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/forensic_delta.py +0 -0
  85. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/golden_scenario.py +0 -0
  86. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
  87. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/host_admin.py +0 -0
  88. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/host_preflight.py +0 -0
  89. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_relocation.py +0 -0
  90. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_activation.py +0 -0
  91. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_admission.py +0 -0
  92. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_backup.py +0 -0
  93. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_composition.py +0 -0
  94. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_executor.py +0 -0
  95. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_migration.py +0 -0
  96. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_operation.py +0 -0
  97. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_plan.py +0 -0
  98. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/installation_update_preparation.py +0 -0
  99. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/investigation_ledger.py +0 -0
  100. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/legacy_inbox_migration.py +0 -0
  101. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/legacy_installation_adoption.py +0 -0
  102. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/lifecycle_worker.py +0 -0
  103. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/live_status.py +0 -0
  104. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/local_api.py +0 -0
  105. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/local_api_keychain.py +0 -0
  106. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/local_repository_binding.py +0 -0
  107. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/managed_autonomy.py +0 -0
  108. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/managed_codex_runtime.py +0 -0
  109. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/operational_installation.py +0 -0
  110. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/operational_installation_lock.py +0 -0
  111. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/operational_installation_record.py +0 -0
  112. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/owner_credential_recovery.py +0 -0
  113. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/parity_context.py +0 -0
  114. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/platform_admin.py +0 -0
  115. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/platform_api.py +0 -0
  116. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/platform_bootstrap.py +0 -0
  117. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/platform_components.py +0 -0
  118. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/pr_check_repair.py +0 -0
  119. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/pr_evidence_backfill.py +0 -0
  120. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/producer.py +0 -0
  121. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/product_installation_readback.py +0 -0
  122. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/project_agent.py +0 -0
  123. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/project_agent_service.py +0 -0
  124. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/project_topology.py +0 -0
  125. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/prompt_history.py +0 -0
  126. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_context.py +0 -0
  127. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_context_benchmark.py +0 -0
  128. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_context_scope.py +0 -0
  129. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_interruption.py +0 -0
  130. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_process_identity.py +0 -0
  131. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_readiness.py +0 -0
  132. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/provider_recovery.py +0 -0
  133. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/providers.py +0 -0
  134. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/qualification.py +0 -0
  135. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/qualification_runtime.py +0 -0
  136. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/recommendation_handoff.py +0 -0
  137. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/reconciliation_adoption.py +0 -0
  138. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/release_operation.py +0 -0
  139. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/report_analysis.py +0 -0
  140. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/repository_attachment.py +0 -0
  141. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/repository_handoff.py +0 -0
  142. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/resources.py +0 -0
  143. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/reviewer_evidence.py +0 -0
  144. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/revision_binding.py +0 -0
  145. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +0 -0
  146. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/schemas/producer-readback-v1.schema.json +0 -0
  147. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
  148. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/server_console_services.py +0 -0
  149. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/server_relay.py +0 -0
  150. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/server_service.py +0 -0
  151. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/status_model.py +0 -0
  152. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/status_reconciliation.py +0 -0
  153. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/submission_cli.py +0 -0
  154. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/submission_intake.py +0 -0
  155. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/system_installation_topology.py +0 -0
  156. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/system_server_service.py +0 -0
  157. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/telemetry.py +0 -0
  158. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/terminal_evidence_reconciliation.py +0 -0
  159. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/validation_identity.py +0 -0
  160. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/validation_profile.py +0 -0
  161. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/version_preparation_delivery.py +0 -0
  162. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/workspace_preflight.py +0 -0
  163. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/worktree_provenance.py +0 -0
  164. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform/worktree_tooling.py +0 -0
  165. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
  166. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/src/engineering_platform.egg-info/top_level.txt +0 -0
  167. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/tests/test_adaptive_action_sizing_design.py +0 -0
  168. {engineering_platform-2.3.79 → engineering_platform-2.3.81}/tests/test_role_task_model_policy_design.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.3.79
3
+ Version: 2.3.81
4
4
  Summary: Local-first Engineering Platform execution operations runtime
5
5
  Author: Peter van Tol
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-platform"
7
- version = "2.3.79"
7
+ version = "2.3.81"
8
8
  description = "Local-first Engineering Platform execution operations runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14,<3.15"
@@ -19,6 +19,7 @@ engineering-platform-server = "engineering_platform.server:main"
19
19
  engineering-platform = "engineering_platform.submission_cli:main"
20
20
  engineering-project-agent = "engineering_platform.project_agent:main"
21
21
  engineering-reconciliation-adopt = "engineering_platform.reconciliation_adoption:main"
22
+ engineering-platform-maintenance = "engineering_platform.central_operational_reset:main"
22
23
 
23
24
  [tool.setuptools]
24
25
  package-dir = {"" = "src"}
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.3.79",
6
+ "version": "2.3.81",
7
7
  "generation": 2,
8
8
  "documentation_namespace": "engineering-platform",
9
9
  "capability_registry_version": 1
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bootstrap_contract": "2026.12",
3
3
  "checkpoint_format": 1,
4
- "dashboard_version": "2.3.79",
4
+ "dashboard_version": "2.3.81",
5
5
  "handoff_protocol": 1,
6
6
  "memory_format": 2,
7
7
  "minimum_codex_cli": "0.146.0",
8
8
  "inbox_protocol": 1,
9
- "platform_version": "2.3.79",
9
+ "platform_version": "2.3.81",
10
10
  "report_format": 2,
11
- "runner_version": "2.3.79",
11
+ "runner_version": "2.3.81",
12
12
  "status_model": 1,
13
13
  "storage_schema": 45,
14
- "watcher_version": "2.3.79"
14
+ "watcher_version": "2.3.81"
15
15
  }
@@ -117,6 +117,7 @@ button:not(.theme-toggle):not(.section-state-toggle):hover:not(:disabled){backgr
117
117
  #dashboardSplash{align-items:center;background:#101015;display:flex;inset:0;justify-content:center;padding:24px;position:fixed;text-align:center;transition:opacity .25s ease,visibility .25s ease;z-index:100}#dashboardSplash[hidden]{display:none}body.dashboard-ready #dashboardSplash{opacity:0;pointer-events:none;visibility:hidden}.dashboard-splash__content{align-items:center;display:flex;flex-direction:column;gap:12px;max-width:360px}.dashboard-splash__title{color:#f7f3ee;font:700 clamp(28px,8vw,42px)/1.1 system-ui;margin:0}.dashboard-splash__version{color:#b79aff;font:600 14px/1.3 system-ui;letter-spacing:.04em;text-transform:uppercase}.dashboard-splash__loading{color:#c8c4cc;font:14px system-ui}.dashboard-splash__spinner{animation:dashboard-splash-spin .85s linear infinite;border:3px solid #332a44;border-radius:50%;border-top-color:#b79aff;height:34px;width:34px}@keyframes dashboard-splash-spin{to{transform:rotate(360deg)}}
118
118
  .dashboard-splash__icon{border-radius:18px;display:block;height:72px;width:72px}
119
119
  #copyToast{align-items:center;background:var(--house-style-surface);border:1px solid var(--house-style);border-radius:999px;box-shadow:0 8px 24px #0009;box-sizing:border-box;color:var(--house-style-contrast);display:flex;font:600 13px system-ui;gap:8px;inset:auto;inset-block-end:max(20px,env(safe-area-inset-bottom));inset-inline-start:50%;margin:0;max-width:calc(100vw - 32px);opacity:0;padding:10px 15px;pointer-events:none;position:fixed;transform:translate(-50%,16px);transition:opacity .16s ease,transform .16s ease;z-index:110}#copyToast::before{content:attr(data-toast-glyph);font:700 17px/1 system-ui}#copyToast.copy-toast--visible{opacity:1;transform:translate(-50%,0)}
120
+ #copyToast[hidden]{display:none}
120
121
  body{overflow-x:hidden}.dashboard-grid,.dashboard-grid>*{min-width:0}.telemetry,.platform-health{min-width:0}.telemetry-scroll{max-width:100%;-webkit-overflow-scrolling:touch}.platform-health__component{min-width:0}.platform-health__component-detail{overflow-wrap:anywhere}.dashboard-titlebar{align-items:center;background:#121217;box-shadow:0 10px 18px #121217;box-sizing:border-box;display:flex;gap:14px;justify-content:space-between;margin:0 0 18px;max-width:100%;padding:8px 16px 12px;position:sticky;top:0;width:100%;z-index:15}.dashboard-titlebar:has(+.dashboard-status-banner:not([hidden])){margin-bottom:0}.dashboard-status-banner{align-items:flex-start;box-sizing:border-box;display:flex;gap:8px;margin:0 0 18px;padding:10px 16px;position:sticky;top:0;width:100%;z-index:14}.dashboard-status-banner[hidden]{display:none}.dashboard-status-banner--usage-limit,.dashboard-status-banner--usage-warning,.dashboard-status-banner--usage-critical{box-shadow:0 10px 18px #121217;font:600 13px/1.45 system-ui}.dashboard-status-banner--usage-limit{background:color-mix(in srgb,var(--house-style) 24%,#241b16);border-bottom:1px solid var(--house-style);color:#fff0dc}.dashboard-status-banner--usage-limit strong{color:var(--house-style)}.dashboard-status-banner--usage-warning{background:#5b4316;border-bottom:1px solid #f4c34f;color:#fff4d6}.dashboard-status-banner--usage-warning strong{color:#ffd768}.dashboard-status-banner--usage-critical{background:#5b1d27;border-bottom:1px solid #ff6b7d;color:#ffe9ed}.dashboard-status-banner--usage-critical strong{color:#ff99a7}.dashboard-status-banner strong{flex:none}.dashboard-status-banner span{min-width:0}.dashboard-titlebar__brand{align-items:center;display:flex;flex:1 1 260px;gap:10px;min-width:0}.dashboard-titlebar h1{font-size:28px;line-height:1.1;margin:0;overflow-wrap:anywhere}.dashboard-titlebar__actions{align-items:center;display:flex;flex:none;gap:12px}.auto-refresh-toggle{align-items:center;color:#b9b6c0;cursor:pointer;display:flex;flex:none;font-size:12px;gap:7px;white-space:nowrap}.auto-refresh-toggle input{accent-color:#54d6a0;height:18px;margin:0;width:18px}.section-state-toggle{align-items:center;background:transparent;border:0;color:#b9b6c0;cursor:pointer;display:flex;font:12px system-ui;gap:7px;margin:0;min-height:44px;min-width:44px;padding:0;position:relative}.section-state-toggle::before{background:#4a4a55;border-radius:999px;content:"";height:24px;transition:background .18s ease;width:42px}.section-state-toggle::after{background:#f7f3ee;border-radius:50%;box-shadow:0 1px 3px #0008;content:"";height:18px;left:3px;position:absolute;top:13px;transform:translateX(0);transition:transform .18s ease;width:18px}.section-state-toggle[aria-checked="true"]::before{background:#8dc7ff}.section-state-toggle[aria-checked="true"]::after{transform:translateX(18px)}.section-state-toggle:focus-visible{box-shadow:none;outline:2px solid #8dc7ff;outline-offset:3px}@media (max-width:1400px){.dashboard-titlebar{align-items:flex-start;flex-wrap:wrap}.dashboard-titlebar__actions{flex:1 1 100%;flex-wrap:wrap;justify-content:flex-end}}@media (max-width:760px){.dashboard-titlebar__actions{justify-content:flex-start}}@media (max-width:620px){.dashboard-status-banner{align-items:stretch;flex-direction:column;margin-bottom:12px;padding:10px 14px}.dashboard-titlebar h1{font-size:25px}.auto-refresh-toggle span,.section-state-toggle__label{display:none}.dashboard-titlebar__actions{gap:8px}}html[data-theme="light"] .dashboard-status-banner--usage-limit{background:color-mix(in srgb,var(--house-style) 18%,#fff);color:#643a13}html[data-theme="light"] .dashboard-status-banner--usage-warning{background:#fff4d6;color:#654600}html[data-theme="light"] .dashboard-status-banner--usage-critical{background:#ffe8eb;color:#7d1726}
121
122
  .dashboard-titlebar__brand{align-items:center;display:flex;gap:10px;min-width:0}.dashboard-app-icon{border-radius:9px;display:block;flex:none;height:32px;width:32px}@media (max-width:620px){.dashboard-app-icon{height:28px;width:28px}}
122
123
  .inbox-queue{--category-color:#818cf8;background:#24242d;border:2px solid var(--category-color);border-radius:18px;box-shadow:0 5px 24px #0006;cursor:pointer;padding:14px}.inbox-queue>summary{border-bottom:1px solid var(--category-color);box-sizing:border-box;cursor:pointer;display:block;list-style:none;margin-bottom:8px;min-height:35px;padding:0 40px 10px 0;position:relative}.inbox-queue>summary::-webkit-details-marker{display:none}.inbox-queue>summary::before{color:var(--category-color);content:"▸ ";font-size:24px;line-height:1;margin:0;padding:0;position:absolute;right:0;top:0;vertical-align:-2px}.inbox-queue[open]>summary::before{content:"▾ "}.inbox-queue>summary>strong{color:var(--category-color);font-size:17px;font-weight:700;letter-spacing:.04em;line-height:1.25;text-transform:uppercase}.inbox-queue .category-description{color:#c9c7e2;font-size:14px;line-height:1.4;margin:12px 0}.inbox-queue .estimate-meta{color:#c9c7e2}.inbox-queue .queue-item{border-top-color:#48446f}
@@ -1212,7 +1213,7 @@ html[data-theme="light"] .dashboard-health__tooltip .dashboard-health__reason{co
1212
1213
  /* A missing provider is a blocking error. The generic button hover/focus
1213
1214
  rule above otherwise wins by specificity and turns the repair action
1214
1215
  orange, which incorrectly reads as a recoverable warning. */
1215
- .dashboard-status-banner--provider-unavailable .provider-readiness-banner__action{background:#7a2230!important;border-color:#ff91a1!important;color:#fff0f2!important}
1216
+ .dashboard-status-banner--provider-unavailable .provider-readiness-banner__action{background:#7a2230!important;border-color:#ff91a1!important;color:#ffb4c0!important}
1216
1217
  .dashboard-status-banner--provider-unavailable .provider-readiness-banner__action:hover:not(:disabled){background:#a92b40!important;border-color:#ffc0c9!important;color:#fff!important}
1217
1218
  .dashboard-status-banner--provider-unavailable .provider-readiness-banner__action:is(:focus,:focus-visible){box-shadow:0 0 0 4px #ff91a188!important;outline:2px solid #ff91a1!important;outline-color:#ff91a1!important;outline-offset:2px!important}
1218
1219
  :is(#codexProviderReadinessBanner,#githubProviderReadinessBanner).dashboard-status-banner--provider-unavailable .provider-readiness-banner__action:is(:focus,:focus-visible){box-shadow:0 0 0 4px #ff91a188!important;outline:2px solid #ff91a1!important;outline-color:#ff91a1!important;outline-offset:2px!important}
@@ -1329,3 +1330,4 @@ html[data-theme="light"] :is(.dashboard-status-banner--provider-auth_required .p
1329
1330
  background-color:color-mix(in srgb,#b89025 14%,#fff)!important;
1330
1331
  }
1331
1332
  .telemetry-note{color:var(--dashboard-muted,#b9b6c0);font-size:12px;line-height:1.45;margin:0 0 10px}.telemetry-run-detail{border-top:1px solid color-mix(in srgb,var(--category-color,#fb7185) 45%,transparent);display:grid;gap:16px;margin-top:16px;padding-top:16px}.telemetry-run-detail h4{color:var(--category-color,#fb7185);font-size:14px;margin:14px 0 8px}.telemetry-scope-switcher{display:flex;flex-wrap:wrap;gap:8px}.telemetry-scope-switcher button{background:var(--dashboard-expanded-content-surface,#24242d);border:1px solid color-mix(in srgb,var(--category-color,#fb7185) 72%,transparent);border-radius:7px;color:inherit;cursor:pointer;font:400 12px/1.2 system-ui;min-height:32px;padding:5px 9px}.telemetry-scope-switcher button:hover{background:color-mix(in srgb,var(--category-color,#fb7185) 14%,var(--dashboard-expanded-content-surface,#24242d));border-color:var(--category-color,#fb7185)}.telemetry-scope-switcher button[aria-selected="true"]{background:color-mix(in srgb,var(--category-color,#fb7185) 24%,var(--dashboard-expanded-content-surface,#24242d));border-color:var(--category-color,#fb7185);box-shadow:inset 3px 0 0 var(--category-color,#fb7185)}.telemetry-scope-switcher button:focus:not(:focus-visible){box-shadow:none!important;outline:0!important}.telemetry-scope-switcher button[aria-selected="true"]:focus:not(:focus-visible){box-shadow:inset 3px 0 0 var(--category-color,#fb7185)!important}.telemetry-timeline{display:grid;gap:5px;list-style:none;margin:0;padding:0}.telemetry-timeline li{border-left:2px solid color-mix(in srgb,var(--category-color,#fb7185) 60%,transparent);margin-left:calc(var(--timeline-depth,0) * 14px);padding:6px 8px;word-break:break-word}@media(max-width:620px){.telemetry-detail-modal__content .technical-grid{grid-template-columns:minmax(0,1fr)}.telemetry-scope-switcher{position:sticky;top:0;background:var(--dashboard-expanded-content-surface,#24242d);padding-block:6px;z-index:2}.telemetry-timeline li{margin-left:min(calc(var(--timeline-depth,0) * 9px),45px)}}
1333
+ .telemetry-timeline-axis{color:var(--dashboard-muted,#b9b6c0);display:flex;font:11px ui-monospace,monospace;justify-content:space-between;margin:4px 8px}.telemetry-timeline__label{font-size:12px;line-height:1.35}.telemetry-timeline__track{background:color-mix(in srgb,var(--category-color,#fb7185) 10%,transparent);border:1px solid color-mix(in srgb,var(--category-color,#fb7185) 24%,transparent);border-radius:4px;height:14px;margin:5px 0;overflow:hidden;position:relative}.telemetry-timeline__bar{background:var(--category-color,#fb7185);border-radius:3px;display:block;height:100%;min-width:2px;position:absolute}.telemetry-timeline__bar:focus-visible{outline:3px solid var(--house-style);outline-offset:2px}.telemetry-timeline__bar--unpositioned{background:repeating-linear-gradient(135deg,var(--category-color,#fb7185) 0 4px,transparent 4px 8px);inset:0;width:100%}.telemetry-timeline__identity{color:var(--dashboard-muted,#b9b6c0);display:block;font-size:10px;overflow-wrap:anywhere}.telemetry-export{min-width:38px}@media(max-width:620px){.telemetry-timeline-axis{margin-inline:0}.telemetry-timeline__identity{font-size:9px}.telemetry-actions{flex-wrap:wrap}}
@@ -4355,16 +4355,23 @@ const executionTelemetryColumns = [
4355
4355
  ["blocked_count", "telemetry.blocked"], ["failed_count", "telemetry.failed"],
4356
4356
  ];
4357
4357
  const EXECUTION_TELEMETRY_PAGE_SIZE = 7;
4358
- let executionTelemetryRows = [], executionTelemetryPage = 1, executionTelemetrySort = { key: "date", direction: "desc" };
4358
+ let executionTelemetryRows = [], executionTelemetryPage = 1, executionTelemetrySort = { key: "date", direction: "desc" },
4359
+ executionTelemetryExportSnapshot = null;
4359
4360
  function telemetryComparableValue(row, key) {
4360
4361
  const value = row?.[key];
4361
- return key === "date" ? String(value || "") : Number.isFinite(Number(value)) ? Number(value) : -1;
4362
+ return key === "date" ? String(value || "") : typeof value === "number" && Number.isFinite(value) ? value : null;
4362
4363
  }
4363
4364
  function sortedExecutionTelemetryRows() {
4364
4365
  const { key, direction } = executionTelemetrySort, multiplier = direction === "asc" ? 1 : -1;
4365
4366
  return [...executionTelemetryRows].sort((left, right) => {
4366
4367
  const leftValue = telemetryComparableValue(left, key), rightValue = telemetryComparableValue(right, key);
4367
- return typeof leftValue === "number"
4368
+ if (leftValue == null && rightValue == null) {
4369
+ const leftDate = String(left?.date || ""), rightDate = String(right?.date || "");
4370
+ return leftDate < rightDate ? -1 : leftDate > rightDate ? 1 : 0;
4371
+ }
4372
+ if (leftValue == null) return 1;
4373
+ if (rightValue == null) return -1;
4374
+ return typeof leftValue === "number" && typeof rightValue === "number"
4368
4375
  ? (leftValue - rightValue) * multiplier
4369
4376
  : (leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0) * multiplier;
4370
4377
  });
@@ -4391,6 +4398,7 @@ function setExecutionTelemetrySort(key) {
4391
4398
  ? { key, direction: executionTelemetrySort.direction === "asc" ? "desc" : "asc" }
4392
4399
  : { key, direction: key === "date" ? "desc" : "asc" };
4393
4400
  executionTelemetryPage = 1;
4401
+ executionTelemetryExportSnapshot = null;
4394
4402
  executionTelemetry(executionTelemetryRows);
4395
4403
  }
4396
4404
  function executionTelemetryText(rows = sortedExecutionTelemetryRows()) {
@@ -4402,15 +4410,74 @@ function executionTelemetryText(rows = sortedExecutionTelemetryRows()) {
4402
4410
  ]);
4403
4411
  return [headings, ...values].map((line) => line.map((value) => String(value ?? "—").replaceAll("\t", " ").replaceAll("\n", " ")).join("\t")).join("\n");
4404
4412
  }
4405
- function downloadExecutionTelemetry() {
4413
+ function downloadTelemetryResponse(url, fallbackName) {
4414
+ // Start the server-owned response while the user activation is still live.
4415
+ // Fetching first and creating a Blob after a network await causes Chromium
4416
+ // to suppress the download on touch and other activation-gated surfaces.
4417
+ // Content-Disposition supplies the authoritative filename. A same-origin
4418
+ // hidden target keeps an HTTP error inspectable without navigating the
4419
+ // Console; successful attachment responses remain native downloads.
4420
+ let frame = $("telemetryExportDownloadTarget");
4421
+ if (!frame) {
4422
+ frame = document.createElement("iframe");
4423
+ frame.id = "telemetryExportDownloadTarget";
4424
+ frame.name = "telemetry-export-download-target";
4425
+ frame.hidden = true;
4426
+ frame.title = t("telemetry.export_download_target");
4427
+ document.body.append(frame);
4428
+ }
4429
+ const inspectFailure = () => {
4430
+ try {
4431
+ if (!frame.contentWindow || frame.contentWindow.location.href === "about:blank") return;
4432
+ const text = frame.contentDocument?.body?.textContent?.trim();
4433
+ if (text) showDashboardToast(t("telemetry.export_failed"), DASHBOARD_TOAST_GLYPHS.error);
4434
+ } catch {
4435
+ showDashboardToast(t("telemetry.export_failed"), DASHBOARD_TOAST_GLYPHS.error);
4436
+ }
4437
+ };
4438
+ if (!frame.dataset.failureListenerInstalled) {
4439
+ frame.dataset.failureListenerInstalled = "true";
4440
+ frame.addEventListener("load", inspectFailure);
4441
+ }
4442
+ const link = document.createElement("a");
4443
+ link.href = url; link.target = frame.name; link.hidden = true;
4444
+ link.setAttribute("aria-label", fallbackName);
4445
+ document.body.append(link); link.click(); link.remove();
4446
+ return Promise.resolve();
4447
+ }
4448
+ async function downloadExecutionTelemetry(format) {
4406
4449
  if (!executionTelemetryRows.length) return;
4407
- const blob = new Blob([executionTelemetryText()], { type: "text/tab-separated-values;charset=utf-8" });
4408
- const url = URL.createObjectURL(blob), link = document.createElement("a");
4409
- link.href = url;
4410
- link.download = "execution-host-telemetry.tsv";
4411
- link.click();
4412
- URL.revokeObjectURL(url);
4413
- void recordUserAction("telemetry_downloaded");
4450
+ const markdown = format === "markdown", project = document.body.dataset.projectId || "";
4451
+ const parameters = new URLSearchParams({
4452
+ project, format: markdown ? "markdown" : "json", locale: dashboardLocale,
4453
+ sort: executionTelemetrySort.key, direction: executionTelemetrySort.direction,
4454
+ });
4455
+ const buttons = document.querySelectorAll("#executionTelemetry .telemetry-export");
4456
+ buttons.forEach((button) => button.disabled = true);
4457
+ try {
4458
+ const bindingParameters = new URLSearchParams(parameters);
4459
+ bindingParameters.delete("format");
4460
+ const binding = `/api/telemetry/export?${bindingParameters}`;
4461
+ if (!executionTelemetryExportSnapshot || executionTelemetryExportSnapshot.binding !== binding) {
4462
+ const prepareParameters = new URLSearchParams(parameters);
4463
+ prepareParameters.set("prepare", "1");
4464
+ prepareParameters.delete("format");
4465
+ const response = await fetch(`/api/telemetry/export?${prepareParameters}`, { cache: "no-store" });
4466
+ const prepared = await response.json().catch(() => ({}));
4467
+ if (!response.ok || typeof prepared.snapshot_id !== "string") throw new Error(t("telemetry.export_failed"));
4468
+ executionTelemetryExportSnapshot = { binding, id: prepared.snapshot_id };
4469
+ }
4470
+ parameters.set("snapshot_id", executionTelemetryExportSnapshot.id);
4471
+ await downloadTelemetryResponse(
4472
+ `/api/telemetry/export?${parameters}`,
4473
+ `telemetry-overview-${project || "project"}-utc.${markdown ? "md" : "json"}`,
4474
+ );
4475
+ void recordUserAction("telemetry_downloaded");
4476
+ } catch (error) {
4477
+ showDashboardToast(t("telemetry.export_failed"), DASHBOARD_TOAST_GLYPHS.error);
4478
+ } finally {
4479
+ buttons.forEach((button) => button.disabled = !executionTelemetryRows.length);
4480
+ }
4414
4481
  }
4415
4482
  async function copyExecutionTelemetry() {
4416
4483
  if (!executionTelemetryRows.length) return;
@@ -4441,6 +4508,7 @@ async function clearExecutionTelemetry() {
4441
4508
  }
4442
4509
  }
4443
4510
  function executionTelemetry(rows) {
4511
+ if (rows !== executionTelemetryRows) executionTelemetryExportSnapshot = null;
4444
4512
  let panel = $("executionTelemetry"),
4445
4513
  body = $("executionTelemetryRows"),
4446
4514
  pagination = $("executionTelemetryPagination");
@@ -4458,7 +4526,8 @@ function executionTelemetry(rows) {
4458
4526
  tableBody = document.createElement("tbody"),
4459
4527
  navigation = document.createElement("nav"),
4460
4528
  actions = document.createElement("div"),
4461
- download = document.createElement("button"),
4529
+ downloadMarkdown = document.createElement("button"),
4530
+ downloadJson = document.createElement("button"),
4462
4531
  copy = document.createElement("button"),
4463
4532
  clear = document.createElement("button"),
4464
4533
  retention = document.createElement("div"),
@@ -4518,7 +4587,8 @@ function executionTelemetry(rows) {
4518
4587
  navigation.setAttribute("aria-label", t("telemetry.pagination_label"));
4519
4588
  actions.className = "log-card-actions telemetry-actions";
4520
4589
  for (const [button, className, glyph, label, handler] of [
4521
- [download, "dashboard-action dashboard-action--download telemetry-download", "↓", "telemetry.download", downloadExecutionTelemetry],
4590
+ [downloadMarkdown, "dashboard-action dashboard-action--download telemetry-export telemetry-export--markdown", "MD", "telemetry.download_overview_markdown", () => downloadExecutionTelemetry("markdown")],
4591
+ [downloadJson, "dashboard-action dashboard-action--download telemetry-export telemetry-export--json", "{}", "telemetry.download_overview_json", () => downloadExecutionTelemetry("json")],
4522
4592
  [copy, "dashboard-action dashboard-action--copy telemetry-copy", "⧉", "telemetry.copy", copyExecutionTelemetry],
4523
4593
  ...(!CENTRAL_CONSOLE ? [[clear, "dashboard-action dashboard-action--destructive telemetry-clear", "⊠", "telemetry.clear_title", clearExecutionTelemetry]] : []),
4524
4594
  ]) {
@@ -4618,7 +4688,8 @@ function executionTelemetry(rows) {
4618
4688
  }
4619
4689
  updateExecutionTelemetrySortHeaders();
4620
4690
  }
4621
- let telemetryDetailTrigger = null, telemetryDetailPayload = null, telemetryDetailDate = null, telemetryDetailRequestId = 0;
4691
+ let telemetryDetailTrigger = null, telemetryDetailPayload = null, telemetryDetailDate = null, telemetryDetailRequestId = 0,
4692
+ telemetryDetailSelection = { scope: "UTC_DAY_DETAIL", runId: null }, telemetryDetailExportSnapshot = null;
4622
4693
  function telemetryMs(value) { return typeof value === "number" && value >= 0 ? telemetryDuration(value / 1000) : t("format.unavailable"); }
4623
4694
  function telemetryNumber(value) {
4624
4695
  return typeof value === "number" && Number.isFinite(value)
@@ -4638,9 +4709,8 @@ function telemetryMetricValue(metric) {
4638
4709
  return `${telemetryNumber(metric.value)} · ${telemetryCoverage(metric)}`;
4639
4710
  }
4640
4711
  function telemetryPercent(value) {
4641
- const percent = Number(value);
4642
- return Number.isFinite(percent)
4643
- ? locale.number(percent, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + "%"
4712
+ return typeof value === "number" && Number.isFinite(value)
4713
+ ? locale.number(value, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + "%"
4644
4714
  : t("format.unavailable");
4645
4715
  }
4646
4716
  function telemetryMetric(label, value) {
@@ -4648,10 +4718,6 @@ function telemetryMetric(label, value) {
4648
4718
  field.append(Object.assign(document.createElement("span"), { className: "label", textContent: label }), Object.assign(document.createElement("strong"), { textContent: telemetryMs(value) }));
4649
4719
  return field;
4650
4720
  }
4651
- function telemetryRunMetric(value, phaseTelemetry) {
4652
- if (typeof value === "number" && value >= 0) return telemetryMs(value);
4653
- return phaseTelemetry === "RECORDED" ? t("telemetry.not_executed") : t("telemetry.not_recorded_short");
4654
- }
4655
4721
  function telemetryLabel(phase) { return t("telemetry.phase." + String(phase || "").toLowerCase(), {}, String(phase || t("format.unavailable"))); }
4656
4722
  function telemetryDetailSortableTable(columns, rows, initialSort, appendRow) {
4657
4723
  let sort = initialSort;
@@ -4715,100 +4781,42 @@ function setTelemetryDetailDownloads(payload, date) {
4715
4781
  button.setAttribute("aria-label", t(label, { date: telemetryDate(date) }));
4716
4782
  }
4717
4783
  }
4718
- function telemetryMarkdownCell(value) {
4719
- return String(value ?? t("format.unavailable")).replaceAll("|", "\\|").replaceAll(/[\r\n]+/g, " ").trim();
4720
- }
4721
- function telemetryMarkdownTable(headings, rows) {
4722
- return [
4723
- `| ${headings.map(telemetryMarkdownCell).join(" | ")} |`,
4724
- `| ${headings.map(() => "---").join(" | ")} |`,
4725
- ...rows.map((row) => `| ${row.map(telemetryMarkdownCell).join(" | ")} |`),
4726
- ].join("\n");
4727
- }
4728
- function telemetryDetailMarkdown(detail, date) {
4729
- const summary = detail?.summary || {}, phases = Array.isArray(detail?.inclusive_phases) ? detail.inclusive_phases : Array.isArray(detail?.phases) ? detail.phases : [];
4730
- const exclusive = Array.isArray(detail?.exclusive_distribution) ? detail.exclusive_distribution : [];
4731
- const runs = Array.isArray(detail?.runs) ? detail.runs : [], bottlenecks = detail?.bottlenecks || {};
4732
- const usage = summary.usage || {};
4733
- const summaryRows = [
4734
- [t("telemetry.executions"), summary.executions ?? 0], [t("telemetry.complete"), summary.completed ?? 0],
4735
- [t("telemetry.blocked"), summary.blocked ?? 0], [t("telemetry.failed"), summary.failed ?? 0],
4736
- [t("telemetry.population"), `N=${summary.population ?? summary.executions ?? 0}`],
4737
- [t("telemetry.elapsed"), telemetryMs(summary.total_wall_time?.average_ms)],
4738
- [t("telemetry.provider_unique_coverage"), telemetryMs(summary.provider_unique_coverage?.average_ms)],
4739
- [t("telemetry.provider_cumulative_process"), telemetryMs(summary.provider_cumulative_process?.average_ms)],
4740
- [t("telemetry.external_wait"), telemetryMs(summary.external_wait?.average_ms)],
4741
- [t("telemetry.unassigned"), telemetryMs(summary.unassigned?.average_ms)],
4742
- [t("telemetry.observed_input"), telemetryMetricValue(usage.input_tokens)],
4743
- [t("telemetry.observed_output"), telemetryMetricValue(usage.output_tokens)],
4744
- [t("telemetry.cache_ratio"), telemetryPercent(summary.cache_ratio_percent)],
4745
- [t("telemetry.contract_version"), detail?.contract_version || t("format.unavailable")],
4746
- ];
4747
- const phaseRows = phases.map((phase) => [telemetryLabel(phase.phase), telemetryMs(phase.average_ms), telemetryMs(phase.median_ms), telemetryMs(phase.total_ms), telemetryPercent(phase.share_percent), phase.runs]);
4748
- const exclusiveRows = exclusive.map((row) => [telemetryLabel(row.category), telemetryMs(row.duration_ms), telemetryPercent(row.share_percent)]);
4749
- const runRows = runs.map((run) => [
4750
- run.run_id, run.started_at ? locale.dateTime(new Date(run.started_at)) : t("format.unavailable"), translate(run.status),
4751
- telemetryMs(run.total_duration_ms), telemetryRunMetric(run.provider_duration_ms, run.phase_telemetry),
4752
- telemetryRunMetric(run.external_wait_ms, run.phase_telemetry), telemetryRunMetric(run.unassigned_ms, run.phase_telemetry),
4753
- telemetryNumber(run.input_tokens), telemetryNumber(run.output_tokens), telemetryPercent(run.cache_ratio_percent),
4754
- run.largest_phase ? telemetryLabel(run.largest_phase) : telemetryRunMetric(null, run.phase_telemetry), run.producer_type,
4755
- run.repository, run.model,
4756
- ]);
4757
- const bottleneckLines = [
4758
- `${t("telemetry.longest_average_phase")}: ${bottlenecks.longest_average_phase ? telemetryLabel(bottlenecks.longest_average_phase.phase) : t("format.unavailable")}`,
4759
- `${t("telemetry.largest_accumulated_phase")}: ${bottlenecks.largest_accumulated_phase ? telemetryLabel(bottlenecks.largest_accumulated_phase.phase) : t("format.unavailable")}`,
4760
- `${t("telemetry.longest_individual_span")}: ${bottlenecks.longest_individual_span ? bottlenecks.longest_individual_span.label || telemetryLabel(bottlenecks.longest_individual_span.phase) : t("format.unavailable")}`,
4761
- ...(Array.isArray(bottlenecks.top_time_consumers) ? bottlenecks.top_time_consumers.map((item) => `${telemetryLabel(item.phase)} — ${telemetryPercent(item.share_percent)}`) : []),
4762
- ];
4763
- const runDetails = [];
4764
- for (const run of runs) {
4765
- const snapshot = run.telemetry_snapshot || {}, attempt = snapshot.attempt || {}, chain = snapshot.chain || {};
4766
- const invocations = Array.isArray(attempt.usage?.invocations) ? attempt.usage.invocations : [];
4767
- runDetails.push(`## ${t("telemetry.scope_attempt")} — ${run.run_id}`, "",
4768
- `${t("telemetry.measurement_coverage")}: ${attempt.timing?.coverage?.state || t("format.unavailable")}`, "",
4769
- invocations.length ? telemetryMarkdownTable(
4770
- [t("telemetry.phase"), t("telemetry.role"), t("telemetry.model"), t("telemetry.duration"), t("telemetry.input"), t("telemetry.cached_input"), t("telemetry.uncached_input"), t("telemetry.output"), t("telemetry.usage_coverage"), t("telemetry.invocation_id")],
4771
- invocations.map((item) => [telemetryLabel(item.phase), item.role, `${item.model || t("format.unavailable")} (${item.model_provenance || t("format.unavailable")})`, telemetryMs(item.duration_ms), telemetryNumber(item.input_tokens), telemetryNumber(item.cached_input_tokens), telemetryNumber(item.uncached_input_tokens), telemetryNumber(item.output_tokens), item.usage_coverage, item.invocation_id]),
4772
- ) : t("format.unavailable"), "",
4773
- `### ${t("telemetry.scope_chain")}`, "",
4774
- telemetryMarkdownTable([t("table.details"), t("history.markdown_value")], [
4775
- [t("telemetry.chain_coverage"), chain.coverage || t("format.unavailable")],
4776
- [t("telemetry.chain_attempts"), chain.attempt_count ?? t("format.unavailable")],
4777
- [t("telemetry.original_attempts"), chain.original_attempt_count ?? t("format.unavailable")],
4778
- [t("telemetry.retries"), chain.retry_count ?? t("format.unavailable")],
4779
- [t("telemetry.resumes"), chain.resume_count ?? t("format.unavailable")],
4780
- [t("telemetry.chain_elapsed"), telemetryMs(chain.elapsed_ms)],
4781
- [t("telemetry.chain_processing"), telemetryMs(chain.processing_time_ms)],
4782
- [t("telemetry.inter_attempt_gaps"), telemetryMs(chain.inter_attempt_gap_ms)],
4783
- [t("telemetry.outside_window"), chain.outside_selected_window_count ?? 0],
4784
- ]), "");
4785
- }
4786
- return [
4787
- `# ${t("telemetry.detail_title", { date: telemetryDate(date) })}`, "", t("telemetry.detail_description"), "",
4788
- `## ${t("telemetry.summary")}`, "", telemetryMarkdownTable([t("table.details"), t("history.markdown_value")], summaryRows), "",
4789
- `## ${t("telemetry.inclusive_title")}`, "", t("telemetry.inclusive_help"), "", phaseRows.length ? telemetryMarkdownTable([t("telemetry.phase"), t("telemetry.average"), t("telemetry.median"), t("telemetry.accumulated"), t("telemetry.share"), t("telemetry.runs")], phaseRows) : t("telemetry.not_recorded"), "",
4790
- `## ${t("telemetry.exclusive_title")}`, "", t("telemetry.exclusive_help"), "", exclusiveRows.length ? telemetryMarkdownTable([t("telemetry.category"), t("telemetry.duration"), t("telemetry.share")], exclusiveRows) : t("telemetry.not_recorded"), "",
4791
- `## ${t("telemetry.bottlenecks")}`, "", ...bottleneckLines.map((line) => `- ${line}`), "",
4792
- `## ${t("telemetry.runs")}`, "", runRows.length ? telemetryMarkdownTable([t("telemetry.run_id"), t("telemetry.start_time"), t("telemetry.status"), t("telemetry.duration"), t("telemetry.provider_unique_coverage"), t("telemetry.external_wait"), t("telemetry.unassigned"), t("telemetry.input"), t("telemetry.output"), t("telemetry.cache_ratio"), t("telemetry.largest_phase"), t("telemetry.producer_type"), t("telemetry.target_repository"), t("telemetry.model")], runRows) : t("format.unavailable"), "",
4793
- ...runDetails,
4794
- ].join("\n");
4795
- }
4796
- function downloadTelemetryDetail(format) {
4784
+ async function downloadTelemetryDetail(format) {
4797
4785
  if (!telemetryDetailPayload || !telemetryDetailDate) return;
4798
- const markdown = format === "markdown";
4799
- const content = markdown
4800
- ? telemetryDetailMarkdown(telemetryDetailPayload, telemetryDetailDate)
4801
- : JSON.stringify(telemetryDetailPayload, null, 2) + "\n";
4802
- const url = URL.createObjectURL(new Blob([content], { type: markdown ? "text/markdown;charset=utf-8" : "application/json;charset=utf-8" }));
4803
- const link = document.createElement("a");
4804
- link.href = url;
4805
- link.download = `execution-telemetry-${telemetryDetailDate}.${markdown ? "md" : "json"}`;
4806
- link.hidden = true;
4807
- document.body.append(link);
4808
- link.click();
4809
- link.remove();
4810
- setTimeout(() => URL.revokeObjectURL(url), 0);
4811
- void recordUserAction(markdown ? "telemetry_detail_markdown_downloaded" : "telemetry_detail_json_downloaded");
4786
+ const markdown = format === "markdown", project = document.body.dataset.projectId || "";
4787
+ const captured = { date: telemetryDetailDate, ...telemetryDetailSelection };
4788
+ const parameters = new URLSearchParams({
4789
+ project, format: markdown ? "markdown" : "json", locale: dashboardLocale,
4790
+ scope: captured.scope,
4791
+ });
4792
+ if (captured.runId) parameters.set("run_id", captured.runId);
4793
+ const buttons = [$('telemetryDetailDownloadMarkdown'), $('telemetryDetailDownloadJson')];
4794
+ buttons.forEach((button) => button.disabled = true);
4795
+ try {
4796
+ const binding = `${captured.date}:${captured.scope}:${captured.runId || ""}:${project}:${dashboardLocale}`;
4797
+ if (!telemetryDetailExportSnapshot || telemetryDetailExportSnapshot.binding !== binding) {
4798
+ const prepareParameters = new URLSearchParams(parameters);
4799
+ prepareParameters.set("prepare", "1");
4800
+ prepareParameters.delete("format");
4801
+ const response = await fetch(
4802
+ `/api/telemetry/${encodeURIComponent(captured.date)}/export?${prepareParameters}`,
4803
+ { cache: "no-store" },
4804
+ );
4805
+ const prepared = await response.json().catch(() => ({}));
4806
+ if (!response.ok || typeof prepared.snapshot_id !== "string") throw new Error(t("telemetry.export_failed"));
4807
+ telemetryDetailExportSnapshot = { binding, id: prepared.snapshot_id };
4808
+ }
4809
+ parameters.set("snapshot_id", telemetryDetailExportSnapshot.id);
4810
+ await downloadTelemetryResponse(
4811
+ `/api/telemetry/${encodeURIComponent(captured.date)}/export?${parameters}`,
4812
+ `telemetry-detail-${project || "project"}-${captured.runId || captured.date}.${markdown ? "md" : "json"}`,
4813
+ );
4814
+ void recordUserAction(markdown ? "telemetry_detail_markdown_downloaded" : "telemetry_detail_json_downloaded");
4815
+ } catch (error) {
4816
+ showDashboardToast(t("telemetry.export_failed"), DASHBOARD_TOAST_GLYPHS.error);
4817
+ } finally {
4818
+ buttons.forEach((button) => button.disabled = false);
4819
+ }
4812
4820
  }
4813
4821
  function closeTelemetryDetail() { const modal = $("telemetryDetailModal"); if (modal.open) modal.close(); }
4814
4822
  function telemetryDetailTableScroll(table, label) {
@@ -4825,6 +4833,8 @@ function openTelemetryDetail(date, trigger) {
4825
4833
  void recordUserAction("telemetry_detail_opened");
4826
4834
  const requestId = ++telemetryDetailRequestId;
4827
4835
  telemetryDetailTrigger = trigger || document.activeElement;
4836
+ telemetryDetailSelection = { scope: "UTC_DAY_DETAIL", runId: null };
4837
+ telemetryDetailExportSnapshot = null;
4828
4838
  const modal = $("telemetryDetailModal"), content = $("telemetryDetailContent");
4829
4839
  $("telemetryDetailTitle").textContent = t("telemetry.detail_title", { date: telemetryDate(date) });
4830
4840
  $("telemetryDetailDescription").textContent = t("telemetry.detail_description");
@@ -4847,7 +4857,7 @@ function telemetryTextField(label, value, title) {
4847
4857
  if (title) { strong.title = title; strong.tabIndex = 0; }
4848
4858
  field.append(name, strong); return field;
4849
4859
  }
4850
- function renderCanonicalRunDetail(run, host) {
4860
+ function renderCanonicalRunDetail(run, host, selectForExport = false) {
4851
4861
  host.replaceChildren();
4852
4862
  const snapshot = run?.telemetry_snapshot || {}, attempt = snapshot.attempt || {}, chain = snapshot.chain || {};
4853
4863
  const timing = attempt.timing || {}, usage = attempt.usage || {}, invocations = Array.isArray(usage.invocations) ? usage.invocations : [];
@@ -4856,11 +4866,15 @@ function renderCanonicalRunDetail(run, host) {
4856
4866
  const attemptButton = Object.assign(document.createElement("button"), { type: "button", textContent: t("telemetry.scope_attempt") });
4857
4867
  const chainButton = Object.assign(document.createElement("button"), { type: "button", textContent: t("telemetry.scope_chain") });
4858
4868
  attemptButton.setAttribute("role", "tab"); chainButton.setAttribute("role", "tab");
4859
- const select = (showChain) => {
4869
+ const select = (showChain, updateExportSelection = selectForExport) => {
4860
4870
  attemptPanel.hidden = showChain; chainPanel.hidden = !showChain;
4861
4871
  attemptButton.setAttribute("aria-selected", String(!showChain)); chainButton.setAttribute("aria-selected", String(showChain));
4872
+ if (updateExportSelection) telemetryDetailSelection = {
4873
+ scope: showChain ? "EXECUTION_CHAIN" : "EP_RUN_ATTEMPT", runId: run.run_id,
4874
+ };
4875
+ if (updateExportSelection) telemetryDetailExportSnapshot = null;
4862
4876
  };
4863
- attemptButton.addEventListener("click", () => select(false)); chainButton.addEventListener("click", () => select(true));
4877
+ attemptButton.addEventListener("click", () => select(false, true)); chainButton.addEventListener("click", () => select(true, true));
4864
4878
  switcher.append(attemptButton, chainButton); select(false); host.append(switcher);
4865
4879
 
4866
4880
  const attemptGrid = document.createElement("div"); attemptGrid.className = "technical-grid";
@@ -4889,37 +4903,57 @@ function renderCanonicalRunDetail(run, host) {
4889
4903
  if (timeline.length) {
4890
4904
  const byId = new Map(timeline.map((span) => [span.phase_id, span]));
4891
4905
  const depth = (span) => { let value = 0, parent = span.parent_phase_id, seen = new Set(); while (parent && byId.has(parent) && !seen.has(parent)) { seen.add(parent); value += 1; parent = byId.get(parent).parent_phase_id; } return value; };
4906
+ const axisDuration = timing.timeline_axis?.duration_ms;
4907
+ const axis = document.createElement("div"); axis.className = "telemetry-timeline-axis";
4908
+ axis.append(
4909
+ Object.assign(document.createElement("span"), { textContent: telemetryMs(0) }),
4910
+ Object.assign(document.createElement("span"), { textContent: telemetryMs(axisDuration) }),
4911
+ );
4912
+ timelineSection.append(axis);
4892
4913
  const list = document.createElement("ol"); list.className = "telemetry-timeline";
4893
4914
  timeline.forEach((span) => {
4894
4915
  const item = document.createElement("li"); item.style.setProperty("--timeline-depth", String(depth(span)));
4895
- item.textContent = `${telemetryLabel(span.phase_name)} · ${telemetryMs(span.duration_ms)} · ${translate(span.outcome)}`;
4896
- item.title = String(span.phase_id || ""); list.append(item);
4916
+ const label = document.createElement("div"); label.className = "telemetry-timeline__label";
4917
+ label.textContent = `${telemetryLabel(span.phase_name)} · ${telemetryMs(span.duration_ms)} · ${translate(span.outcome)} · ${span.measurement_basis || t("format.unavailable")}`;
4918
+ const track = document.createElement("div"); track.className = "telemetry-timeline__track";
4919
+ const bar = document.createElement("span"); bar.className = "telemetry-timeline__bar";
4920
+ if (typeof axisDuration === "number" && axisDuration > 0 && typeof span.relative_start_ms === "number" && typeof span.relative_end_ms === "number") {
4921
+ bar.style.left = `${Math.max(0, Math.min(100, span.relative_start_ms * 100 / axisDuration))}%`;
4922
+ bar.style.width = `${Math.max(.25, Math.min(100, (span.relative_end_ms - span.relative_start_ms) * 100 / axisDuration))}%`;
4923
+ } else bar.classList.add("telemetry-timeline__bar--unpositioned");
4924
+ bar.title = `${span.phase_id || t("format.unavailable")} · ${span.started_at || t("format.unavailable")} — ${span.completed_at || t("format.unavailable")}`;
4925
+ bar.tabIndex = 0; track.append(bar);
4926
+ const identity = document.createElement("code"); identity.className = "telemetry-timeline__identity";
4927
+ identity.textContent = `${span.phase_id || t("format.unavailable")}${span.parent_phase_id ? ` ← ${span.parent_phase_id}` : ""}`;
4928
+ item.append(label, track, identity); list.append(item);
4897
4929
  });
4898
4930
  timelineSection.append(list);
4899
- if (timing.timeline_truncated) timelineSection.append(Object.assign(document.createElement("p"), { className: "telemetry-note", textContent: t("telemetry.timeline_truncated", { count: timing.timeline_limit }) }));
4931
+ if (timing.timeline_truncated) timelineSection.append(Object.assign(document.createElement("p"), { className: "telemetry-note", textContent: t("telemetry.timeline_truncated_full_export", { shown: timeline.length, total: timing.timeline_observation_count }) }));
4900
4932
  } else timelineSection.append(Object.assign(document.createElement("p"), { textContent: t("telemetry.not_recorded") }));
4901
4933
  attemptPanel.append(timelineSection);
4902
4934
 
4903
4935
  const invocationSection = document.createElement("section");
4904
- invocationSection.append(Object.assign(document.createElement("h4"), { textContent: `${t("telemetry.invocations")} (${invocations.length})` }));
4936
+ invocationSection.append(Object.assign(document.createElement("h4"), { textContent: `${t("telemetry.invocations")} (${usage.invocation_observation_count ?? invocations.length})` }));
4905
4937
  if (invocations.length) {
4906
4938
  const columns = [
4907
4939
  ["phase", "telemetry.phase"], ["role", "telemetry.role"], ["model", "telemetry.model"], ["duration_ms", "telemetry.duration", "desc"],
4908
4940
  ["input_tokens", "telemetry.input", "desc"], ["cached_input_tokens", "telemetry.cached_input"], ["uncached_input_tokens", "telemetry.uncached_input"],
4909
- ["output_tokens", "telemetry.output"], ["usage_coverage", "telemetry.usage_coverage"], ["invocation_id", "telemetry.invocation_id"], ["retry_ordinal", "telemetry.retry_identity"],
4910
- ].map(([key, label, defaultDirection]) => ({ key, label, defaultDirection, value: (item) => key === "phase" ? telemetryLabel(item.phase) : Number.isFinite(Number(item[key])) ? Number(item[key]) : String(item[key] || "") }));
4941
+ ["output_tokens", "telemetry.output"], ["usage_coverage", "telemetry.usage_coverage"], ["timing_correlation", "telemetry.timing_correlation"], ["invocation_id", "telemetry.invocation_id"], ["retry_ordinal", "telemetry.retry_identity"],
4942
+ ].map(([key, label, defaultDirection]) => ({ key, label, defaultDirection, value: (item) => key === "phase" ? telemetryLabel(item.phase) : typeof item[key] === "number" && Number.isFinite(item[key]) ? item[key] : String(item[key] || "") }));
4911
4943
  const table = telemetryDetailSortableTable(columns, invocations, { key: "input_tokens", direction: "desc" }, (item, body) => {
4912
4944
  const row = document.createElement("tr");
4913
4945
  const model = item.model ? `${item.model} (${item.model_provenance || t("format.unavailable")})` : t("format.unavailable");
4914
- [telemetryLabel(item.phase), item.role || t("format.unavailable"), model, telemetryMs(item.duration_ms), telemetryNumber(item.input_tokens), telemetryNumber(item.cached_input_tokens), telemetryNumber(item.uncached_input_tokens), telemetryNumber(item.output_tokens), item.usage_coverage || t("format.unavailable"), item.invocation_id || t("format.unavailable"), item.retry_ordinal ?? t("format.unavailable")]
4946
+ [telemetryLabel(item.phase), item.role || t("format.unavailable"), model, telemetryMs(item.duration_ms), telemetryNumber(item.input_tokens), telemetryNumber(item.cached_input_tokens), telemetryNumber(item.uncached_input_tokens), telemetryNumber(item.output_tokens), item.usage_coverage || t("format.unavailable"), item.timing_correlation || t("format.unavailable"), item.invocation_id || t("format.unavailable"), item.retry_ordinal ?? t("format.unavailable")]
4915
4947
  .forEach((value) => row.append(Object.assign(document.createElement("td"), { textContent: String(value) })));
4916
4948
  body.append(row);
4917
4949
  });
4918
4950
  invocationSection.append(telemetryDetailTableScroll(table, t("telemetry.invocations")));
4951
+ if (usage.invocation_table_truncated) invocationSection.append(Object.assign(document.createElement("p"), { className: "telemetry-note", textContent: t("telemetry.invocations_truncated_full_export", { shown: invocations.length, total: usage.invocation_observation_count }) }));
4919
4952
  } else invocationSection.append(Object.assign(document.createElement("p"), { textContent: t("telemetry.not_recorded") }));
4920
4953
  attemptPanel.append(invocationSection);
4921
4954
 
4922
4955
  const chainGrid = document.createElement("div"); chainGrid.className = "technical-grid";
4956
+ const chainMetric = (name) => chain.usage_metrics?.[name];
4923
4957
  chainGrid.append(
4924
4958
  telemetryTextField(t("telemetry.chain_coverage"), chain.coverage, Array.isArray(chain.reasons) ? chain.reasons.join("; ") : chain.reason),
4925
4959
  telemetryTextField(t("telemetry.chain_attempts"), chain.attempt_count),
@@ -4931,6 +4965,12 @@ function renderCanonicalRunDetail(run, host) {
4931
4965
  telemetryTextField(t("telemetry.inter_attempt_gaps"), telemetryMs(chain.inter_attempt_gap_ms)),
4932
4966
  telemetryTextField(t("telemetry.outside_window"), chain.outside_selected_window_count ?? 0),
4933
4967
  telemetryTextField(t("telemetry.mission_scope"), chain.mission_scope_label || t("format.unavailable")),
4968
+ telemetryTextField(t("telemetry.invocations"), chain.provider_invocation_count ?? t("format.unavailable")),
4969
+ telemetryTextField(t("telemetry.observed_input"), telemetryMetricValue(chainMetric("input_tokens")), chainMetric("input_tokens")?.missing_reason),
4970
+ telemetryTextField(t("telemetry.cached_input"), telemetryMetricValue(chainMetric("cached_input_tokens")), chainMetric("cached_input_tokens")?.missing_reason),
4971
+ telemetryTextField(t("telemetry.uncached_input"), telemetryMetricValue(chainMetric("uncached_input_tokens")), chainMetric("uncached_input_tokens")?.missing_reason),
4972
+ telemetryTextField(t("telemetry.observed_output"), telemetryMetricValue(chainMetric("output_tokens")), chainMetric("output_tokens")?.missing_reason),
4973
+ telemetryTextField(t("telemetry.cache_ratio"), telemetryPercent(chain.cache_ratio_percent), telemetryCoverage(chain.cache_ratio_population)),
4934
4974
  );
4935
4975
  chainPanel.append(chainGrid);
4936
4976
  const chainRuns = Array.isArray(chain.runs) ? chain.runs : [];
@@ -4975,7 +5015,7 @@ function renderTelemetryDetail(detail, content) {
4975
5015
  : [categoryKey, "duration_ms", "share_percent"];
4976
5016
  const table = telemetryDetailSortableTable(columns.map((label, index) => ({
4977
5017
  key: columnKeys[index], label,
4978
- value: (item) => index === 0 ? telemetryLabel(item[categoryKey]) : Number(item[columnKeys[index]]) || 0,
5018
+ value: (item) => index === 0 ? telemetryLabel(item[categoryKey]) : typeof item[columnKeys[index]] === "number" && Number.isFinite(item[columnKeys[index]]) ? item[columnKeys[index]] : null,
4979
5019
  })), values, { key: categoryKey, direction: "asc" }, (item, body) => {
4980
5020
  const row = document.createElement("tr"), category = telemetryLabel(item[categoryKey]);
4981
5021
  const rendered = durationKey === "total_ms"
@@ -5012,11 +5052,11 @@ function renderTelemetryDetail(detail, content) {
5012
5052
  ["provider_duration_ms", "telemetry.provider_unique_coverage"], ["external_wait_ms", "telemetry.external_wait"], ["unassigned_ms", "telemetry.unassigned"],
5013
5053
  ["input_tokens", "telemetry.input"], ["output_tokens", "telemetry.output"], ["cache_ratio_percent", "telemetry.cache_ratio"], ["largest_phase", "telemetry.largest_phase"],
5014
5054
  ["model", "telemetry.model"],
5015
- ].map(([key, label, defaultDirection]) => ({ key, label, defaultDirection, value: (run) => key === "largest_phase" ? telemetryLabel(run[key]) : key === "started_at" ? Date.parse(run[key]) || 0 : Number.isFinite(Number(run[key])) ? Number(run[key]) : String(run[key] || "") }));
5055
+ ].map(([key, label, defaultDirection]) => ({ key, label, defaultDirection, value: (run) => key === "largest_phase" ? telemetryLabel(run[key]) : key === "started_at" ? Date.parse(run[key]) || null : typeof run[key] === "number" && Number.isFinite(run[key]) ? run[key] : String(run[key] || "") }));
5016
5056
  const runTable = telemetryDetailSortableTable(runColumns, runs, { key: "started_at", direction: "desc" }, (run, runBody) => {
5017
5057
  const row = document.createElement("tr"), id = document.createElement("button"); row.className = "telemetry-row"; row.tabIndex = 0; row.setAttribute("role", "button");
5018
5058
  row.setAttribute("aria-label", String(run.run_id || t("format.unavailable"))); id.type = "button"; id.className = "telemetry-run-link"; id.textContent = run.run_id;
5019
- const selectRun = () => { runBody.querySelectorAll('.telemetry-row[data-selected="true"]').forEach((candidate) => { candidate.dataset.selected = "false"; }); row.dataset.selected = "true"; renderCanonicalRunDetail(run, runDetailHost); };
5059
+ const selectRun = () => { runBody.querySelectorAll('.telemetry-row[data-selected="true"]').forEach((candidate) => { candidate.dataset.selected = "false"; }); row.dataset.selected = "true"; renderCanonicalRunDetail(run, runDetailHost, true); };
5020
5060
  row.addEventListener("click", selectRun); row.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); selectRun(); } });
5021
5061
  id.addEventListener("click", (event) => { event.stopPropagation(); selectRun(); openPromptHistoryDetail({ run_id: run.run_id, title: run.run_id }); });
5022
5062
  [id, run.started_at ? locale.dateTime(new Date(run.started_at)) : t("format.unavailable"), translate(run.status), telemetryMs(run.total_duration_ms), telemetryMs(run.provider_duration_ms), telemetryMs(run.external_wait_ms), telemetryMs(run.unassigned_ms), telemetryNumber(run.input_tokens), telemetryNumber(run.output_tokens), telemetryPercent(run.cache_ratio_percent), run.largest_phase ? telemetryLabel(run.largest_phase) : t("format.unavailable"), run.model || t("format.unavailable")].forEach((value) => { const cell = document.createElement("td"); if (value instanceof Element) cell.append(value); else cell.textContent = String(value); row.append(cell); });
@@ -3150,6 +3150,11 @@ Object.assign(DASHBOARD_MESSAGES.es, {
3150
3150
  "log_event.terminal_report_corrected":"Informe final corregido", "log_event.active_run_lease_reconciled":"Arrendamiento de ejecución activa conciliado", "log_event.detached_runner_job_missing":"Falta la tarea de ejecución desconectada", "log_event.inbox_scan":"Bandeja de entrada analizada", "log_event.active_transaction_queue_refreshed":"Cola de ejecución activa actualizada",
3151
3151
  "log_event.inbox_log_unavailable":"Registro del monitor de bandeja de entrada no disponible", "log_event.dashboard_log_unavailable":"Registro del panel de estado no disponible"
3152
3152
  });
3153
+ Object.assign(DASHBOARD_MESSAGES.en, {"telemetry.download_overview_markdown":"Download complete overview as Markdown","telemetry.download_overview_json":"Download complete overview as JSON","telemetry.export_failed":"The telemetry export could not be created. Try again.","telemetry.export_download_target":"Telemetry export download response","telemetry.timeline_truncated_full_export":"Showing {shown} of {total} spans. The complete detail export contains all spans.","telemetry.invocations_truncated_full_export":"Showing {shown} of {total} invocations. The complete detail export contains all invocations.","telemetry.timing_correlation":"Timing correlation"});
3154
+ Object.assign(DASHBOARD_MESSAGES.nl, {"telemetry.download_overview_markdown":"Volledig overzicht als Markdown downloaden","telemetry.download_overview_json":"Volledig overzicht als JSON downloaden","telemetry.export_failed":"De telemetrie-export kon niet worden gemaakt. Probeer opnieuw.","telemetry.export_download_target":"Downloadrespons voor telemetrie-export","telemetry.timeline_truncated_full_export":"{shown} van {total} spans worden getoond. De volledige detailexport bevat alle spans.","telemetry.invocations_truncated_full_export":"{shown} van {total} invocations worden getoond. De volledige detailexport bevat alle invocations.","telemetry.timing_correlation":"Timingkoppeling"});
3155
+ Object.assign(DASHBOARD_MESSAGES.de, {"telemetry.download_overview_markdown":"Vollständige Übersicht als Markdown herunterladen","telemetry.download_overview_json":"Vollständige Übersicht als JSON herunterladen","telemetry.export_failed":"Der Telemetrieexport konnte nicht erstellt werden. Versuchen Sie es erneut.","telemetry.export_download_target":"Downloadantwort des Telemetrieexports","telemetry.timeline_truncated_full_export":"{shown} von {total} Zeitspannen werden angezeigt. Der vollständige Detailexport enthält alle Zeitspannen.","telemetry.invocations_truncated_full_export":"{shown} von {total} Aufrufen werden angezeigt. Der vollständige Detailexport enthält alle Aufrufe.","telemetry.timing_correlation":"Zeitkorrelation"});
3156
+ Object.assign(DASHBOARD_MESSAGES.fr, {"telemetry.download_overview_markdown":"Télécharger la vue complète en Markdown","telemetry.download_overview_json":"Télécharger la vue complète en JSON","telemetry.export_failed":"L’export de télémétrie n’a pas pu être créé. Réessayez.","telemetry.export_download_target":"Réponse de téléchargement de l’export de télémétrie","telemetry.timeline_truncated_full_export":"{shown} intervalles sur {total} sont affichés. L’export détaillé complet contient tous les intervalles.","telemetry.invocations_truncated_full_export":"{shown} invocations sur {total} sont affichées. L’export détaillé complet contient toutes les invocations.","telemetry.timing_correlation":"Corrélation temporelle"});
3157
+ Object.assign(DASHBOARD_MESSAGES.es, {"telemetry.download_overview_markdown":"Descargar el resumen completo como Markdown","telemetry.download_overview_json":"Descargar el resumen completo como JSON","telemetry.export_failed":"No se pudo crear la exportación de telemetría. Inténtalo de nuevo.","telemetry.export_download_target":"Respuesta de descarga de la exportación de telemetría","telemetry.timeline_truncated_full_export":"Se muestran {shown} de {total} tramos. La exportación detallada completa contiene todos los tramos.","telemetry.invocations_truncated_full_export":"Se muestran {shown} de {total} invocaciones. La exportación detallada completa contiene todas las invocaciones.","telemetry.timing_correlation":"Correlación temporal"});
3153
3158
  Object.assign(DASHBOARD_MESSAGES.en, {
3154
3159
  "action.recover": "Recover",
3155
3160
  "preflight.sync": "Preflight failed: {reason}\nRecovery: {recovery}",