create-windy 0.4.0 → 0.5.0

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 (194) hide show
  1. package/dist/cli.js +263 -121
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
  5. package/template/apps/agent-server/adk_web_agents/services.py +25 -0
  6. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
  7. package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
  8. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
  9. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
  10. package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
  11. package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
  14. package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
  15. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
  16. package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
  18. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
  25. package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
  26. package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
  27. package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
  28. package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
  29. package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
  30. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
  31. package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
  32. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
  33. package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
  34. package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
  35. package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
  36. package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
  37. package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
  38. package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
  39. package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
  40. package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
  41. package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
  42. package/template/apps/agent-server/tests/test_report_store.py +286 -0
  43. package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
  44. package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
  45. package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
  46. package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
  47. package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
  48. package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
  49. package/template/apps/server/src/agent/debug-session-id.ts +37 -0
  50. package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
  51. package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
  52. package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
  53. package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
  54. package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
  55. package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
  56. package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
  57. package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
  58. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  59. package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
  60. package/template/apps/server/src/agent/run-authorization.ts +32 -1
  61. package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
  62. package/template/apps/server/src/agent/run-contracts.ts +5 -0
  63. package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
  64. package/template/apps/server/src/agent/run-facade.test.ts +78 -0
  65. package/template/apps/server/src/agent/run-facade.ts +33 -3
  66. package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
  67. package/template/apps/server/src/agent/run-input-verification.ts +41 -0
  68. package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
  69. package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
  70. package/template/apps/server/src/agent/tool-host.test.ts +1 -0
  71. package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
  72. package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
  73. package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
  74. package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
  75. package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
  76. package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
  77. package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
  78. package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
  79. package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
  80. package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
  81. package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
  82. package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
  83. package/template/apps/server/src/license/runtime-service.test.ts +6 -0
  84. package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
  85. package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
  86. package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
  87. package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
  88. package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
  89. package/template/apps/server/src/module-host/initialize.ts +6 -197
  90. package/template/apps/server/src/module-host/request-context.ts +1 -0
  91. package/template/apps/server/src/module-host/scoped-host.ts +214 -0
  92. package/template/apps/server/src/module-host.ts +1 -0
  93. package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
  94. package/template/apps/server/src/runtime-adk-debug.ts +8 -2
  95. package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
  96. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
  97. package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
  98. package/template/apps/server/src/settings/watermark-routes.ts +29 -1
  99. package/template/apps/server/src/system/operations.test.ts +54 -0
  100. package/template/apps/server/src/system/operations.ts +23 -1
  101. package/template/apps/server/src/system/seed.test.ts +4 -0
  102. package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
  103. package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
  104. package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
  105. package/template/apps/web/src/composables/watermark-settings.ts +40 -11
  106. package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
  107. package/template/apps/web/src/layout/AppSidebar.vue +6 -1
  108. package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
  109. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
  110. package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
  111. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
  112. package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
  113. package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
  114. package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
  115. package/template/apps/web/src/pages/settings/WatermarkPreview.vue +72 -0
  116. package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
  117. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
  118. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
  119. package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
  120. package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
  121. package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
  122. package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
  123. package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
  124. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
  125. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
  126. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
  127. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
  128. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
  129. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
  130. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
  131. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
  132. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
  133. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
  134. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
  135. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
  136. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
  137. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
  138. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
  139. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
  140. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
  141. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
  142. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
  143. package/template/apps/web/src/router/error-navigation.ts +22 -0
  144. package/template/apps/web/src/router/index.ts +12 -23
  145. package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
  146. package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
  147. package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
  148. package/template/apps/web/src/services/operations-api.ts +28 -0
  149. package/template/docker-compose.agent-debug.yml +1 -1
  150. package/template/docs/architecture/ai-runtime.md +2 -1
  151. package/template/docs/architecture/module-host.md +3 -0
  152. package/template/docs/platform/agent-run-attestation.md +65 -0
  153. package/template/docs/platform/agent-runtime-observability.md +135 -0
  154. package/template/docs/platform/agent-runtime.md +47 -3
  155. package/template/docs/platform/operations-overview.md +13 -0
  156. package/template/docs/platform/platform-authentication.md +4 -3
  157. package/template/docs/platform/platform-shell-settings.md +15 -10
  158. package/template/docs/platform/web-system-crud.md +1 -1
  159. package/template/packages/config/package.json +1 -1
  160. package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
  161. package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
  162. package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
  163. package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
  164. package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
  165. package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
  166. package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
  167. package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
  168. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  169. package/template/packages/database/package.json +1 -1
  170. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  171. package/template/packages/database/src/schema/agent-runtime.ts +114 -0
  172. package/template/packages/database/src/schema/platform-settings.ts +3 -0
  173. package/template/packages/jobs/package.json +1 -1
  174. package/template/packages/modules/package.json +1 -1
  175. package/template/packages/modules/src/system-admin-routes.ts +10 -0
  176. package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
  177. package/template/packages/modules/src/system-api-control-plane.ts +17 -1
  178. package/template/packages/modules/src/system-audit-actions.ts +10 -0
  179. package/template/packages/modules/src/system-features.ts +8 -0
  180. package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
  181. package/template/packages/modules/src/system-module-catalog.ts +15 -4
  182. package/template/packages/modules/src/system-modules-access.test.ts +69 -0
  183. package/template/packages/modules/src/system-modules.ts +13 -0
  184. package/template/packages/modules/src/system-permissions.ts +2 -0
  185. package/template/packages/server-sdk/index.ts +11 -0
  186. package/template/packages/server-sdk/package.json +2 -1
  187. package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
  188. package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
  189. package/template/packages/server-sdk/src/module-host.ts +5 -0
  190. package/template/packages/shared/index.ts +3 -0
  191. package/template/packages/shared/package.json +1 -1
  192. package/template/packages/shared/src/ai-runtime-report.ts +134 -0
  193. package/template/packages/shared/src/platform-settings.ts +17 -0
  194. package/template/scripts/lib/postgres-regression.ts +201 -0
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from psycopg import AsyncConnection
6
+
7
+ from .report_types import NonTerminalRunStatus
8
+
9
+
10
+ async def start_or_resume_run(
11
+ connection: AsyncConnection[dict[str, Any]],
12
+ run_id: str,
13
+ started_at: float,
14
+ ) -> int:
15
+ async with connection.transaction():
16
+ cursor = await connection.execute(
17
+ """
18
+ update agent_runtime_report_runs
19
+ set status = 'running', started_at = coalesce(started_at, %s),
20
+ updated_at = %s
21
+ where run_id = %s
22
+ and status not in ('completed', 'failed', 'cancelled')
23
+ """,
24
+ (started_at, started_at, run_id),
25
+ )
26
+ if cursor.rowcount != 1:
27
+ existing = await connection.execute(
28
+ """
29
+ select status from agent_runtime_report_runs
30
+ where run_id = %s
31
+ """,
32
+ (run_id,),
33
+ )
34
+ if not await existing.fetchone():
35
+ raise KeyError(run_id)
36
+ return 0
37
+ abandoned = await connection.execute(
38
+ """
39
+ update agent_runtime_report_attempts
40
+ set outcome = 'abandoned', finished_at = %s,
41
+ duration_ms = greatest(0, round((%s - started_at) * 1000))
42
+ where run_id = %s and outcome = 'running'
43
+ and finished_at is null
44
+ """,
45
+ (started_at, started_at, run_id),
46
+ )
47
+ return abandoned.rowcount
48
+
49
+
50
+ async def set_nonterminal_run_status(
51
+ connection: AsyncConnection[dict[str, Any]],
52
+ run_id: str,
53
+ status: NonTerminalRunStatus,
54
+ updated_at: float,
55
+ ) -> None:
56
+ cursor = await connection.execute(
57
+ """
58
+ update agent_runtime_report_runs
59
+ set status = %s, updated_at = %s
60
+ where run_id = %s
61
+ and status not in ('completed', 'failed', 'cancelled')
62
+ and updated_at <= %s
63
+ """,
64
+ (status, updated_at, run_id, updated_at),
65
+ )
66
+ if cursor.rowcount == 1:
67
+ return
68
+ current_cursor = await connection.execute(
69
+ """
70
+ select status, updated_at
71
+ from agent_runtime_report_runs
72
+ where run_id = %s
73
+ """,
74
+ (run_id,),
75
+ )
76
+ current = await current_cursor.fetchone()
77
+ if not current:
78
+ raise KeyError(run_id)
79
+ if current["status"] in {"completed", "failed", "cancelled"}:
80
+ return
81
+ if float(current["updated_at"]) > updated_at:
82
+ return
83
+ raise RuntimeError("Agent Runtime 报表状态更新失败")
@@ -0,0 +1,331 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ import uuid
6
+ from typing import Any, Literal, cast
7
+
8
+ from psycopg import AsyncConnection
9
+ from psycopg.rows import dict_row
10
+ from psycopg_pool import AsyncConnectionPool
11
+
12
+ from .contracts import StartRunRequest
13
+ from .postgres_report_insert import RUN_INSERT_SQL, run_insert_values
14
+ from .postgres_report_queries import get_run, list_runs
15
+ from .postgres_report_replay import assert_attempt_terminal_replay
16
+ from .postgres_report_status import (
17
+ set_nonterminal_run_status,
18
+ start_or_resume_run,
19
+ )
20
+ from .postgres_report_summary import summary
21
+ from .report_metrics import AgentRuntimeMetrics
22
+ from .report_store import AgentRuntimeReportStore
23
+ from .report_types import (
24
+ AttemptReport,
25
+ AttemptTerminalOutcome,
26
+ NonTerminalRunStatus,
27
+ ReportFilter,
28
+ UsageFacts,
29
+ retention_seconds,
30
+ validate_retention,
31
+ )
32
+
33
+ type DatabaseConnection = AsyncConnection[dict[str, Any]]
34
+
35
+
36
+ class PostgresAgentRuntimeReportStore(AgentRuntimeReportStore):
37
+ def __init__(
38
+ self,
39
+ database_url: str,
40
+ retention_days: int = 90,
41
+ *,
42
+ metrics: AgentRuntimeMetrics | None = None,
43
+ ) -> None:
44
+ validate_retention(retention_days)
45
+ self.retention_days = retention_days
46
+ self.metrics = metrics or AgentRuntimeMetrics()
47
+ self._pool = cast(
48
+ AsyncConnectionPool[DatabaseConnection],
49
+ AsyncConnectionPool(
50
+ database_url,
51
+ min_size=1,
52
+ max_size=10,
53
+ open=False,
54
+ kwargs={"row_factory": dict_row},
55
+ ),
56
+ )
57
+ self._open_lock = asyncio.Lock()
58
+ self._opened = False
59
+
60
+ async def ready(self) -> None:
61
+ if self._opened:
62
+ return
63
+ async with self._open_lock:
64
+ if not self._opened:
65
+ await self._pool.open()
66
+ self._opened = True
67
+
68
+ async def ensure_run(
69
+ self,
70
+ run_id: str,
71
+ request: StartRunRequest,
72
+ created_at: float,
73
+ ) -> None:
74
+ await self.ready()
75
+ context = request.reporting
76
+ values = run_insert_values(
77
+ run_id,
78
+ request,
79
+ created_at,
80
+ self.retention_days,
81
+ )
82
+ async with self._pool.connection() as connection:
83
+ await connection.execute(
84
+ "delete from agent_runtime_report_runs where expires_at <= %s",
85
+ (time.time(),),
86
+ )
87
+ cursor = await connection.execute(RUN_INSERT_SQL, values)
88
+ if cursor.rowcount == 1:
89
+ return
90
+ existing_cursor = await connection.execute(
91
+ """
92
+ select operation_key, release_id, trace_id, actor_id
93
+ from agent_runtime_report_runs where run_id = %s
94
+ """,
95
+ (run_id,),
96
+ )
97
+ existing = await existing_cursor.fetchone()
98
+ identity = (
99
+ request.operation_key,
100
+ request.release.release_id,
101
+ request.trace_id,
102
+ context.actor_id if context else None,
103
+ )
104
+ if (
105
+ not existing
106
+ or (
107
+ str(existing["operation_key"]),
108
+ str(existing["release_id"]),
109
+ str(existing["trace_id"]),
110
+ existing["actor_id"],
111
+ )
112
+ != identity
113
+ ):
114
+ raise ValueError("Agent Runtime 报表 Run 身份冲突")
115
+
116
+ async def start_run(self, run_id: str, started_at: float) -> None:
117
+ await self.ready()
118
+ async with self._pool.connection() as connection:
119
+ abandoned = await start_or_resume_run(connection, run_id, started_at)
120
+ for _ in range(abandoned):
121
+ self.metrics.observe_attempt("abandoned", UsageFacts())
122
+
123
+ async def finish_run(
124
+ self,
125
+ run_id: str,
126
+ status: Literal["completed", "failed", "cancelled"],
127
+ finished_at: float,
128
+ safe_error_code: str | None = None,
129
+ ) -> None:
130
+ await self.ready()
131
+ observed = False
132
+ async with self._pool.connection() as connection:
133
+ cursor = await connection.execute(
134
+ """
135
+ update agent_runtime_report_runs
136
+ set status = %s, safe_error_code = %s, finished_at = %s,
137
+ duration_ms = greatest(
138
+ 0, round((%s - coalesce(started_at, created_at)) * 1000)
139
+ ),
140
+ updated_at = %s, expires_at = %s
141
+ where run_id = %s
142
+ and status not in ('completed', 'failed', 'cancelled')
143
+ """,
144
+ (
145
+ status,
146
+ safe_error_code,
147
+ finished_at,
148
+ finished_at,
149
+ finished_at,
150
+ finished_at + retention_seconds(self.retention_days),
151
+ run_id,
152
+ ),
153
+ )
154
+ observed = cursor.rowcount == 1
155
+ if not observed:
156
+ current_cursor = await connection.execute(
157
+ "select status from agent_runtime_report_runs where run_id = %s",
158
+ (run_id,),
159
+ )
160
+ current = await current_cursor.fetchone()
161
+ if not current:
162
+ raise KeyError(run_id)
163
+ if current["status"] != status:
164
+ raise ValueError("Agent Runtime 报表终态冲突")
165
+ if observed:
166
+ self.metrics.observe_run(status)
167
+
168
+ async def set_run_status(
169
+ self,
170
+ run_id: str,
171
+ status: NonTerminalRunStatus,
172
+ updated_at: float,
173
+ ) -> None:
174
+ await self.ready()
175
+ async with self._pool.connection() as connection:
176
+ await set_nonterminal_run_status(connection, run_id, status, updated_at)
177
+
178
+ async def start_attempt(
179
+ self,
180
+ run_id: str,
181
+ tool_round: int,
182
+ deployment_key: str,
183
+ requested_model: str,
184
+ started_at: float,
185
+ ) -> AttemptReport:
186
+ await self.ready()
187
+ attempt_id = f"attempt_{uuid.uuid4().hex}"
188
+ async with self._pool.connection() as connection:
189
+ async with connection.transaction():
190
+ await connection.execute(
191
+ "select pg_advisory_xact_lock(hashtext(%s))",
192
+ (run_id,),
193
+ )
194
+ cursor = await connection.execute(
195
+ """
196
+ select coalesce(max(sequence), 0) + 1 as sequence
197
+ from agent_runtime_report_attempts where run_id = %s
198
+ """,
199
+ (run_id,),
200
+ )
201
+ row = await cursor.fetchone()
202
+ sequence = int(row["sequence"]) if row else 1
203
+ await connection.execute(
204
+ """
205
+ insert into agent_runtime_report_attempts (
206
+ attempt_id, run_id, sequence, tool_round,
207
+ deployment_key, requested_model, outcome,
208
+ cache_status, usage_source, started_at
209
+ ) values (
210
+ %s, %s, %s, %s, %s, %s,
211
+ 'running', 'unknown', 'unknown', %s
212
+ )
213
+ """,
214
+ (
215
+ attempt_id,
216
+ run_id,
217
+ sequence,
218
+ tool_round,
219
+ deployment_key,
220
+ requested_model,
221
+ started_at,
222
+ ),
223
+ )
224
+ return AttemptReport(
225
+ attempt_id=attempt_id,
226
+ run_id=run_id,
227
+ sequence=sequence,
228
+ tool_round=tool_round,
229
+ deployment_key=deployment_key,
230
+ requested_model=requested_model,
231
+ reported_model=None,
232
+ outcome="running",
233
+ usage=UsageFacts(),
234
+ duration_ms=None,
235
+ safe_error_code=None,
236
+ started_at=started_at,
237
+ finished_at=None,
238
+ )
239
+
240
+ async def finish_attempt(
241
+ self,
242
+ attempt_id: str,
243
+ outcome: AttemptTerminalOutcome,
244
+ usage: UsageFacts,
245
+ finished_at: float,
246
+ reported_model: str | None = None,
247
+ safe_error_code: str | None = None,
248
+ ) -> None:
249
+ await self.ready()
250
+ values = (
251
+ outcome,
252
+ usage.cache_status,
253
+ usage.source,
254
+ usage.input_tokens,
255
+ usage.output_tokens,
256
+ usage.cached_input_tokens,
257
+ usage.reasoning_tokens,
258
+ usage.total_tokens,
259
+ usage.first_token_latency_ms,
260
+ reported_model,
261
+ safe_error_code,
262
+ finished_at,
263
+ finished_at,
264
+ attempt_id,
265
+ )
266
+ async with self._pool.connection() as connection:
267
+ cursor = await connection.execute(
268
+ """
269
+ update agent_runtime_report_attempts set
270
+ outcome = %s, cache_status = %s, usage_source = %s,
271
+ input_tokens = %s, output_tokens = %s,
272
+ cached_input_tokens = %s, reasoning_tokens = %s,
273
+ total_tokens = %s, first_token_latency_ms = %s,
274
+ reported_model = %s, safe_error_code = %s,
275
+ duration_ms = greatest(0, round((%s - started_at) * 1000)),
276
+ finished_at = %s
277
+ where attempt_id = %s and finished_at is null
278
+ """,
279
+ values,
280
+ )
281
+ observed = cursor.rowcount == 1
282
+ if not observed:
283
+ current_cursor = await connection.execute(
284
+ """
285
+ select outcome, cache_status, usage_source,
286
+ input_tokens, output_tokens, cached_input_tokens,
287
+ reasoning_tokens, total_tokens, first_token_latency_ms,
288
+ reported_model, safe_error_code
289
+ from agent_runtime_report_attempts
290
+ where attempt_id = %s
291
+ """,
292
+ (attempt_id,),
293
+ )
294
+ current = await current_cursor.fetchone()
295
+ if not current:
296
+ raise KeyError(attempt_id)
297
+ assert_attempt_terminal_replay(
298
+ current,
299
+ outcome,
300
+ usage,
301
+ reported_model,
302
+ safe_error_code,
303
+ )
304
+ if observed:
305
+ self.metrics.observe_attempt(outcome, usage)
306
+
307
+ async def summary(self, query: ReportFilter) -> dict[str, object]:
308
+ await self.ready()
309
+ return await summary(
310
+ self._pool,
311
+ query,
312
+ self.retention_days,
313
+ time.time(),
314
+ )
315
+
316
+ async def list_runs(
317
+ self,
318
+ query: ReportFilter,
319
+ page: int,
320
+ page_size: int,
321
+ ) -> dict[str, object]:
322
+ await self.ready()
323
+ return await list_runs(self._pool, query, page, page_size, time.time())
324
+
325
+ async def get_run(self, run_id: str) -> dict[str, object] | None:
326
+ await self.ready()
327
+ return await get_run(self._pool, run_id, time.time())
328
+
329
+ async def close(self) -> None:
330
+ if self._opened:
331
+ await self._pool.close()