create-windy 0.4.1 → 0.5.1

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 (174) hide show
  1. package/dist/cli.js +292 -125
  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/system/operations.test.ts +54 -0
  96. package/template/apps/server/src/system/operations.ts +23 -1
  97. package/template/apps/server/src/system/seed.test.ts +4 -0
  98. package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
  99. package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
  100. package/template/apps/web/src/layout/AppSidebar.vue +6 -1
  101. package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
  102. package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
  103. package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
  104. package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
  105. package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
  106. package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
  107. package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
  108. package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
  109. package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
  110. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
  111. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
  112. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
  113. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
  114. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
  115. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
  116. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
  117. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
  118. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
  119. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
  120. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
  121. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
  122. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
  123. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
  124. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
  125. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
  126. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
  127. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
  128. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
  129. package/template/apps/web/src/router/error-navigation.ts +22 -0
  130. package/template/apps/web/src/router/index.ts +12 -23
  131. package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
  132. package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
  133. package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
  134. package/template/apps/web/src/services/operations-api.ts +28 -0
  135. package/template/docker-compose.agent-debug.yml +1 -1
  136. package/template/docs/architecture/ai-runtime.md +2 -1
  137. package/template/docs/architecture/module-host.md +3 -0
  138. package/template/docs/platform/agent-run-attestation.md +65 -0
  139. package/template/docs/platform/agent-runtime-observability.md +135 -0
  140. package/template/docs/platform/agent-runtime.md +47 -3
  141. package/template/docs/platform/operations-overview.md +13 -0
  142. package/template/docs/platform/platform-authentication.md +4 -3
  143. package/template/packages/config/package.json +1 -1
  144. package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
  145. package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
  146. package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
  147. package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
  148. package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
  149. package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
  150. package/template/packages/database/drizzle/meta/_journal.json +21 -0
  151. package/template/packages/database/package.json +1 -1
  152. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  153. package/template/packages/database/src/schema/agent-runtime.ts +114 -0
  154. package/template/packages/jobs/package.json +1 -1
  155. package/template/packages/modules/package.json +1 -1
  156. package/template/packages/modules/src/system-admin-routes.ts +10 -0
  157. package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
  158. package/template/packages/modules/src/system-api-control-plane.ts +17 -1
  159. package/template/packages/modules/src/system-audit-actions.ts +10 -0
  160. package/template/packages/modules/src/system-features.ts +8 -0
  161. package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
  162. package/template/packages/modules/src/system-module-catalog.ts +15 -4
  163. package/template/packages/modules/src/system-modules-access.test.ts +69 -0
  164. package/template/packages/modules/src/system-modules.ts +13 -0
  165. package/template/packages/modules/src/system-permissions.ts +2 -0
  166. package/template/packages/server-sdk/index.ts +11 -0
  167. package/template/packages/server-sdk/package.json +2 -1
  168. package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
  169. package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
  170. package/template/packages/server-sdk/src/module-host.ts +5 -0
  171. package/template/packages/shared/index.ts +3 -0
  172. package/template/packages/shared/package.json +1 -1
  173. package/template/packages/shared/src/ai-runtime-report.ts +134 -0
  174. package/template/scripts/lib/postgres-regression.ts +201 -0
@@ -0,0 +1,101 @@
1
+ <script setup lang="ts">
2
+ import { Chart } from "@antv/g2";
3
+ import {
4
+ computed,
5
+ onBeforeUnmount,
6
+ onMounted,
7
+ useTemplateRef,
8
+ watch,
9
+ } from "vue";
10
+ import type { AiRuntimeModelUsage } from "@/services/ai-runtime-api";
11
+ import { formatTokenCount } from "../../ai-runtime-presenter";
12
+
13
+ const props = defineProps<{ models: readonly AiRuntimeModelUsage[] }>();
14
+ const container = useTemplateRef<HTMLDivElement>("container");
15
+ const data = computed(() =>
16
+ props.models.map((model) => ({
17
+ model: model.model,
18
+ totalTokens: model.totalTokens,
19
+ inputTokens: model.inputTokens,
20
+ outputTokens: model.outputTokens,
21
+ cachedInputTokens: model.cachedInputTokens,
22
+ reasoningTokens: model.reasoningTokens,
23
+ })),
24
+ );
25
+ const hasData = computed(() =>
26
+ data.value.some(({ totalTokens }) => (totalTokens ?? 0) > 0),
27
+ );
28
+ let chart: Chart | undefined;
29
+
30
+ onMounted(renderChart);
31
+ watch(data, renderChart);
32
+ onBeforeUnmount(destroyChart);
33
+
34
+ async function renderChart() {
35
+ destroyChart();
36
+ if (!container.value || !hasData.value) return;
37
+ chart = new Chart({ container: container.value, autoFit: true, height: 300 });
38
+ chart.options({
39
+ type: "interval",
40
+ data: data.value,
41
+ encode: { x: "model", y: "totalTokens" },
42
+ style: { fill: "#0f766e" },
43
+ axis: {
44
+ x: { title: false, labelAutoRotate: true },
45
+ y: { title: "Token", labelFormatter: formatTokenCount },
46
+ },
47
+ tooltip: {
48
+ items: [
49
+ {
50
+ field: "totalTokens",
51
+ name: "总 Token",
52
+ valueFormatter: formatTokenCount,
53
+ },
54
+ {
55
+ field: "inputTokens",
56
+ name: "输入",
57
+ valueFormatter: formatTokenCount,
58
+ },
59
+ {
60
+ field: "outputTokens",
61
+ name: "输出",
62
+ valueFormatter: formatTokenCount,
63
+ },
64
+ {
65
+ field: "cachedInputTokens",
66
+ name: "缓存输入(输入子集)",
67
+ valueFormatter: formatTokenCount,
68
+ },
69
+ {
70
+ field: "reasoningTokens",
71
+ name: "推理(输出子集)",
72
+ valueFormatter: formatTokenCount,
73
+ },
74
+ ],
75
+ },
76
+ animate: false,
77
+ });
78
+ await chart.render();
79
+ }
80
+
81
+ function destroyChart() {
82
+ chart?.destroy();
83
+ chart = undefined;
84
+ container.value?.replaceChildren();
85
+ }
86
+ </script>
87
+
88
+ <template>
89
+ <div
90
+ v-if="hasData"
91
+ ref="container"
92
+ class="h-[300px] w-full"
93
+ aria-label="按模型统计的总 Token 柱状图"
94
+ />
95
+ <p
96
+ v-else
97
+ class="grid h-[300px] place-items-center text-sm text-muted-foreground"
98
+ >
99
+ 当前范围内没有已上报的模型 Token 数据。
100
+ </p>
101
+ </template>
@@ -0,0 +1,185 @@
1
+ import { onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
2
+ import {
3
+ getAiRuntimeRun,
4
+ getAiRuntimeSummary,
5
+ listAiRuntimeActors,
6
+ listAiRuntimeRuns,
7
+ type AiRuntimeActor,
8
+ type AiRuntimePage,
9
+ type AiRuntimeRange,
10
+ type AiRuntimeRunDetail,
11
+ type AiRuntimeRunItem,
12
+ type AiRuntimeSummary,
13
+ } from "@/services/ai-runtime-api";
14
+
15
+ const REFRESH_INTERVAL_MS = 60_000;
16
+
17
+ export function useAiRuntimeReport() {
18
+ const range = shallowRef<AiRuntimeRange>("day");
19
+ const actorHandle = shallowRef<string>();
20
+ const page = shallowRef(1);
21
+ const pageSize = shallowRef(20);
22
+ const summary = shallowRef<AiRuntimeSummary>();
23
+ const runs = shallowRef<AiRuntimePage<AiRuntimeRunItem>>();
24
+ const actors = shallowRef<readonly AiRuntimeActor[]>([]);
25
+ const actorQuery = shallowRef("");
26
+ const detail = shallowRef<AiRuntimeRunDetail>();
27
+ const detailOpen = shallowRef(false);
28
+ const loading = shallowRef(false);
29
+ const detailLoading = shallowRef(false);
30
+ const error = shallowRef("");
31
+ const detailError = shallowRef("");
32
+ let requestRevision = 0;
33
+ let detailRequestRevision = 0;
34
+ let actorRequestRevision = 0;
35
+ let detailRequest: AbortController | undefined;
36
+ let timer: ReturnType<typeof setInterval> | undefined;
37
+
38
+ onMounted(async () => {
39
+ await Promise.all([loadActors(), reload()]);
40
+ timer = setInterval(() => void reload(), REFRESH_INTERVAL_MS);
41
+ });
42
+ onBeforeUnmount(() => {
43
+ if (timer) clearInterval(timer);
44
+ actorRequestRevision += 1;
45
+ cancelDetailRequest();
46
+ });
47
+ watch(detailOpen, (open) => {
48
+ if (!open) cancelDetailRequest();
49
+ });
50
+
51
+ async function reload() {
52
+ const revision = ++requestRevision;
53
+ loading.value = true;
54
+ error.value = "";
55
+ const query = { range: range.value, actorHandle: actorHandle.value };
56
+ try {
57
+ const [nextSummary, nextRuns] = await Promise.all([
58
+ getAiRuntimeSummary(query),
59
+ listAiRuntimeRuns({
60
+ ...query,
61
+ page: page.value,
62
+ pageSize: pageSize.value,
63
+ }),
64
+ ]);
65
+ if (revision !== requestRevision) return;
66
+ summary.value = nextSummary;
67
+ runs.value = nextRuns;
68
+ } catch (currentError) {
69
+ if (revision === requestRevision) error.value = message(currentError);
70
+ } finally {
71
+ if (revision === requestRevision) loading.value = false;
72
+ }
73
+ }
74
+
75
+ async function loadActors(query = actorQuery.value) {
76
+ const revision = ++actorRequestRevision;
77
+ try {
78
+ const result = await listAiRuntimeActors(query, 1, 100);
79
+ if (revision === actorRequestRevision) actors.value = result.items;
80
+ } catch {
81
+ if (revision === actorRequestRevision) actors.value = [];
82
+ }
83
+ }
84
+
85
+ async function searchActors(query: string) {
86
+ actorQuery.value = query;
87
+ await loadActors(query);
88
+ }
89
+
90
+ function setRange(value: AiRuntimeRange) {
91
+ if (range.value === value) return;
92
+ range.value = value;
93
+ page.value = 1;
94
+ void reload();
95
+ }
96
+
97
+ function setActor(value: string | undefined) {
98
+ if (actorHandle.value === value) return;
99
+ actorHandle.value = value;
100
+ page.value = 1;
101
+ void reload();
102
+ }
103
+
104
+ function setPage(value: number) {
105
+ if (page.value === value) return;
106
+ page.value = value;
107
+ void reload();
108
+ }
109
+
110
+ function setPageSize(value: number) {
111
+ if (pageSize.value === value) return;
112
+ pageSize.value = value;
113
+ page.value = 1;
114
+ void reload();
115
+ }
116
+
117
+ async function openRun(runId: string) {
118
+ const revision = ++detailRequestRevision;
119
+ detailRequest?.abort();
120
+ const controller = new AbortController();
121
+ detailRequest = controller;
122
+ detail.value = undefined;
123
+ detailError.value = "";
124
+ detailOpen.value = true;
125
+ detailLoading.value = true;
126
+ try {
127
+ const result = await getAiRuntimeRun(runId, controller.signal);
128
+ if (revision === detailRequestRevision && !controller.signal.aborted) {
129
+ detail.value = result;
130
+ }
131
+ } catch (currentError) {
132
+ if (
133
+ revision === detailRequestRevision &&
134
+ !controller.signal.aborted &&
135
+ !isAbortError(currentError)
136
+ ) {
137
+ detailError.value = message(currentError);
138
+ }
139
+ } finally {
140
+ if (revision === detailRequestRevision) {
141
+ detailLoading.value = false;
142
+ detailRequest = undefined;
143
+ }
144
+ }
145
+ }
146
+
147
+ function cancelDetailRequest() {
148
+ detailRequestRevision += 1;
149
+ detailRequest?.abort();
150
+ detailRequest = undefined;
151
+ detailLoading.value = false;
152
+ }
153
+
154
+ return {
155
+ range,
156
+ actorHandle,
157
+ page,
158
+ pageSize,
159
+ summary,
160
+ runs,
161
+ actors,
162
+ actorQuery,
163
+ detail,
164
+ detailOpen,
165
+ loading,
166
+ detailLoading,
167
+ error,
168
+ detailError,
169
+ reload,
170
+ searchActors,
171
+ setRange,
172
+ setActor,
173
+ setPage,
174
+ setPageSize,
175
+ openRun,
176
+ };
177
+ }
178
+
179
+ function message(error: unknown) {
180
+ return error instanceof Error ? error.message : String(error);
181
+ }
182
+
183
+ function isAbortError(error: unknown) {
184
+ return error instanceof Error && error.name === "AbortError";
185
+ }
@@ -0,0 +1,158 @@
1
+ import { flushPromises, mount } from "@vue/test-utils";
2
+ import { defineComponent, h } from "vue";
3
+ import { beforeEach, describe, expect, test, vi } from "vitest";
4
+
5
+ const mocks = vi.hoisted(() => ({
6
+ getAiRuntimeRun: vi.fn(),
7
+ getAiRuntimeSummary: vi.fn(),
8
+ listAiRuntimeActors: vi.fn(),
9
+ listAiRuntimeRuns: vi.fn(),
10
+ }));
11
+ vi.mock("@/services/ai-runtime-api", () => mocks);
12
+
13
+ import { useAiRuntimeReport } from "./useAiRuntimeReport";
14
+
15
+ describe("AI Runtime 报表请求协调", () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ mocks.getAiRuntimeSummary.mockResolvedValue({});
19
+ mocks.listAiRuntimeActors.mockResolvedValue({
20
+ items: [],
21
+ total: 0,
22
+ page: 1,
23
+ pageSize: 100,
24
+ });
25
+ mocks.listAiRuntimeRuns.mockResolvedValue({
26
+ items: [],
27
+ total: 0,
28
+ page: 1,
29
+ pageSize: 20,
30
+ });
31
+ });
32
+
33
+ test("连续打开详情时取消旧请求且旧响应不能覆盖新 Run", async () => {
34
+ const first = deferred<{ runId: string }>();
35
+ const second = deferred<{ runId: string }>();
36
+ const signals: AbortSignal[] = [];
37
+ mocks.getAiRuntimeRun.mockImplementation(
38
+ (runId: string, signal: AbortSignal) => {
39
+ signals.push(signal);
40
+ return runId === "run_first" ? first.promise : second.promise;
41
+ },
42
+ );
43
+ let report!: ReturnType<typeof useAiRuntimeReport>;
44
+ const wrapper = mount(
45
+ defineComponent({
46
+ setup() {
47
+ report = useAiRuntimeReport();
48
+ return () => h("div");
49
+ },
50
+ }),
51
+ );
52
+ await flushPromises();
53
+
54
+ const firstRequest = report.openRun("run_first");
55
+ const secondRequest = report.openRun("run_second");
56
+ second.resolve({ runId: "run_second" });
57
+ await secondRequest;
58
+
59
+ expect(signals[0]?.aborted).toBe(true);
60
+ expect(signals[1]?.aborted).toBe(false);
61
+ expect(report.detail.value?.runId).toBe("run_second");
62
+
63
+ first.resolve({ runId: "run_first" });
64
+ await firstRequest;
65
+ expect(report.detail.value?.runId).toBe("run_second");
66
+ wrapper.unmount();
67
+ });
68
+
69
+ test("连续搜索操作者时旧响应不能覆盖新查询", async () => {
70
+ const first = deferred<{
71
+ items: Array<{ actorHandle: string; label: string }>;
72
+ }>();
73
+ const second = deferred<{
74
+ items: Array<{ actorHandle: string; label: string }>;
75
+ }>();
76
+ mocks.listAiRuntimeActors.mockImplementation((query: string) => {
77
+ if (query === "first") return first.promise;
78
+ if (query === "second") return second.promise;
79
+ return Promise.resolve({ items: [], total: 0, page: 1, pageSize: 100 });
80
+ });
81
+ let report!: ReturnType<typeof useAiRuntimeReport>;
82
+ const wrapper = mount(
83
+ defineComponent({
84
+ setup() {
85
+ report = useAiRuntimeReport();
86
+ return () => h("div");
87
+ },
88
+ }),
89
+ );
90
+ await flushPromises();
91
+
92
+ const firstRequest = report.searchActors("first");
93
+ const secondRequest = report.searchActors("second");
94
+ second.resolve({
95
+ items: [{ actorHandle: "actor-second", label: "第二位操作者" }],
96
+ });
97
+ await secondRequest;
98
+ first.resolve({
99
+ items: [{ actorHandle: "actor-first", label: "第一位操作者" }],
100
+ });
101
+ await firstRequest;
102
+
103
+ expect(report.actorQuery.value).toBe("second");
104
+ expect(report.actors.value).toEqual([
105
+ { actorHandle: "actor-second", label: "第二位操作者" },
106
+ ]);
107
+ wrapper.unmount();
108
+ });
109
+
110
+ test("旧操作者搜索晚失败时不能清空新查询结果", async () => {
111
+ const first = deferred<{
112
+ items: Array<{ actorHandle: string; label: string }>;
113
+ }>();
114
+ const second = deferred<{
115
+ items: Array<{ actorHandle: string; label: string }>;
116
+ }>();
117
+ mocks.listAiRuntimeActors.mockImplementation((query: string) => {
118
+ if (query === "first") return first.promise;
119
+ if (query === "second") return second.promise;
120
+ return Promise.resolve({ items: [], total: 0, page: 1, pageSize: 100 });
121
+ });
122
+ let report!: ReturnType<typeof useAiRuntimeReport>;
123
+ const wrapper = mount(
124
+ defineComponent({
125
+ setup() {
126
+ report = useAiRuntimeReport();
127
+ return () => h("div");
128
+ },
129
+ }),
130
+ );
131
+ await flushPromises();
132
+
133
+ const firstRequest = report.searchActors("first");
134
+ const secondRequest = report.searchActors("second");
135
+ second.resolve({
136
+ items: [{ actorHandle: "actor-second", label: "第二位操作者" }],
137
+ });
138
+ await secondRequest;
139
+ first.reject(new Error("旧查询失败"));
140
+ await firstRequest;
141
+
142
+ expect(report.actorQuery.value).toBe("second");
143
+ expect(report.actors.value).toEqual([
144
+ { actorHandle: "actor-second", label: "第二位操作者" },
145
+ ]);
146
+ wrapper.unmount();
147
+ });
148
+ });
149
+
150
+ function deferred<T>() {
151
+ let resolve!: (value: T) => void;
152
+ let reject!: (reason?: unknown) => void;
153
+ const promise = new Promise<T>((complete, fail) => {
154
+ resolve = complete;
155
+ reject = fail;
156
+ });
157
+ return { promise, reject, resolve };
158
+ }
@@ -15,6 +15,17 @@ import {
15
15
  type SystemRecord,
16
16
  updateSystemResource,
17
17
  } from "@/services/system-api";
18
+ // @windy-module system.audit begin
19
+ import { defaultAuditFilters } from "../audit-time-range";
20
+ // @windy-module system.audit end
21
+
22
+ interface SystemResourceFilters {
23
+ q?: string;
24
+ actorId?: string;
25
+ action?: string;
26
+ occurredAfter?: string;
27
+ occurredBefore?: string;
28
+ }
18
29
 
19
30
  export interface UseSystemResourceOptions {
20
31
  apiResource: string;
@@ -32,13 +43,9 @@ export function useSystemResource(options: UseSystemResourceOptions) {
32
43
  const pageSize = shallowRef(DEFAULT_PAGE_SIZE);
33
44
  const q = shallowRef("");
34
45
  const status = shallowRef("");
35
- const auditFilters = shallowRef<{
36
- q?: string;
37
- actorId?: string;
38
- action?: string;
39
- occurredAfter?: string;
40
- occurredBefore?: string;
41
- }>({});
46
+ const auditFilters = shallowRef<SystemResourceFilters>(
47
+ initialResourceFilters(options.apiResource),
48
+ );
42
49
  const exportOptions = shallowRef<{ includeScheduler?: boolean }>({});
43
50
  const loading = shallowRef(false);
44
51
  const saving = shallowRef(false);
@@ -258,6 +265,13 @@ export function useSystemResource(options: UseSystemResourceOptions) {
258
265
  };
259
266
  }
260
267
 
268
+ function initialResourceFilters(_apiResource: string): SystemResourceFilters {
269
+ // @windy-module system.audit begin
270
+ if (_apiResource === "audit-logs") return defaultAuditFilters();
271
+ // @windy-module system.audit end
272
+ return {};
273
+ }
274
+
261
275
  function downloadJson(filename: string, payload: unknown) {
262
276
  const blob = new Blob([JSON.stringify(payload, null, 2)], {
263
277
  type: "application/json",
@@ -39,6 +39,28 @@ export function safeErrorReturnTo(value: string): string {
39
39
  return target.startsWith("/error/") ? "/" : target;
40
40
  }
41
41
 
42
+ export function deniedRoute(from: string, reason: string) {
43
+ return {
44
+ name: "access-denied",
45
+ query: { from, reason },
46
+ replace: true,
47
+ };
48
+ }
49
+
50
+ export function serviceUnavailableRoute(from: string, reason: string) {
51
+ return {
52
+ name: "service-unavailable",
53
+ query: { from, reason },
54
+ replace: true,
55
+ };
56
+ }
57
+
58
+ export function requestIdFrom(value: unknown): string | undefined {
59
+ if (typeof value !== "string") return undefined;
60
+ const requestId = value.trim();
61
+ return requestId && requestId.length <= 128 ? requestId : undefined;
62
+ }
63
+
42
64
  function scheduleErrorNavigation(
43
65
  router: Router,
44
66
  target: Parameters<Router["replace"]>[0],
@@ -18,6 +18,9 @@ import LicensePage from "@/pages/license/LicensePage.vue";
18
18
  // @windy-module system.operations begin
19
19
  import SystemOperationsPage from "@/pages/system/SystemOperationsPage.vue";
20
20
  // @windy-module system.operations end
21
+ // @windy-module system.agent begin
22
+ import AiRuntimePage from "@/pages/system/AiRuntimePage.vue";
23
+ // @windy-module system.agent end
21
24
  // @windy-module system.notification begin
22
25
  import SystemNotificationsPage from "@/pages/system/SystemNotificationsPage.vue";
23
26
  // @windy-module system.notification end
@@ -40,7 +43,12 @@ import { useLicenseRuntime } from "@/composables/useLicenseRuntime";
40
43
  import { evaluateWebAccess, type WebAccessSnapshot } from "@/layout/navigation";
41
44
  import { isApiErrorStatus } from "@/services/http";
42
45
  import { loginRoute, safeReturnTo } from "./auth-navigation";
43
- import { safeErrorReturnTo } from "./error-navigation";
46
+ import {
47
+ deniedRoute,
48
+ requestIdFrom,
49
+ safeErrorReturnTo,
50
+ serviceUnavailableRoute,
51
+ } from "./error-navigation";
44
52
  import {
45
53
  assertNoRouteCollisions,
46
54
  materializeBusinessAdminRoutes,
@@ -88,6 +96,9 @@ const platformAdminPageComponents: Partial<
88
96
  // @windy-module system.operations begin
89
97
  operations: SystemOperationsPage,
90
98
  // @windy-module system.operations end
99
+ // @windy-module system.agent begin
100
+ "ai-runtime": AiRuntimePage,
101
+ // @windy-module system.agent end
91
102
  // @windy-module system.configuration begin
92
103
  configuration: ModuleConfigurationPage,
93
104
  // @windy-module system.configuration end
@@ -323,26 +334,4 @@ export function createAppRouter(
323
334
  return appRouter;
324
335
  }
325
336
 
326
- function deniedRoute(from: string, reason: string) {
327
- return {
328
- name: "access-denied",
329
- query: { from, reason },
330
- replace: true,
331
- };
332
- }
333
-
334
- function serviceUnavailableRoute(from: string, reason: string) {
335
- return {
336
- name: "service-unavailable",
337
- query: { from, reason },
338
- replace: true,
339
- };
340
- }
341
-
342
- function requestIdFrom(value: unknown): string | undefined {
343
- if (typeof value !== "string") return undefined;
344
- const requestId = value.trim();
345
- return requestId && requestId.length <= 128 ? requestId : undefined;
346
- }
347
-
348
337
  export const router = createAppRouter();
@@ -7,6 +7,7 @@ export type PlatformAdminPageKey =
7
7
  | "license"
8
8
  | "notification"
9
9
  | "operations"
10
+ | "ai-runtime"
10
11
  | "configuration";
11
12
 
12
13
  export interface PlatformAdminRouteBinding {
@@ -52,6 +53,9 @@ export const platformAdminRouteBindings: readonly PlatformAdminRouteBinding[] =
52
53
  // @windy-module system.operations begin
53
54
  { routeKey: "system.ops", page: "operations" },
54
55
  // @windy-module system.operations end
56
+ // @windy-module system.agent begin
57
+ { routeKey: "system.ai-runtime", page: "ai-runtime" },
58
+ // @windy-module system.agent end
55
59
  // @windy-module system.configuration begin
56
60
  { routeKey: "system.config", page: "configuration" },
57
61
  // @windy-module system.configuration end