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
@@ -133,6 +133,7 @@ function unavailableModuleAiOperationPort(
133
133
  };
134
134
  return {
135
135
  startAgent: async () => unavailable(),
136
+ verifyAgentRun: async () => unavailable(),
136
137
  startDocument: async () => unavailable(),
137
138
  startSpeech: async () => unavailable(),
138
139
  embed: async () => unavailable(),
@@ -0,0 +1,214 @@
1
+ import type { DurableHandlerRegistry } from "@southwind-ai/jobs";
2
+ import type { ModuleManifest } from "@southwind-ai/modules";
3
+ import {
4
+ requireModuleNamespace,
5
+ type ModuleHost,
6
+ type ModuleResourceFileAccessPolicy,
7
+ type ModuleUploadPolicy,
8
+ } from "@southwind-ai/server-sdk";
9
+ import type {
10
+ RegisteredModuleAiOperationHandler,
11
+ RegisteredModuleDebugSessionProvider,
12
+ RegisteredModuleDurableHandler,
13
+ RegisteredModuleGovernedResource,
14
+ RegisteredModuleRolePreset,
15
+ RegisteredModuleRoute,
16
+ RegisteredModuleSandboxToolHandler,
17
+ RegisteredModuleSearchProvider,
18
+ RegisteredModuleTaskHandler,
19
+ RegisteredModuleToolHandler,
20
+ } from "./initialize-contracts.js";
21
+ import { validateDebugSessionProviderRegistration } from "./debug-session-provider-registration.js";
22
+ import { registerModuleDurableHandler } from "./durable-handler-registration.js";
23
+ import { registerGovernedResources } from "./governed-resources.js";
24
+ import { registerResourceFileAccessPolicies } from "./resource-file-policies.js";
25
+ import { validateRouteDeclaration } from "./route-declaration.js";
26
+ import { validateSearchProviderRegistration } from "./search-provider-registration.js";
27
+
28
+ type HandlerInstaller = (registry: DurableHandlerRegistry) => void;
29
+
30
+ export interface HostCollections {
31
+ uploadPolicies: ModuleUploadPolicy[];
32
+ uploadPurposes: Set<string>;
33
+ modulePurposes: Map<string, Set<string>>;
34
+ moduleDependencies: ReadonlyMap<string, readonly string[]>;
35
+ resourceFileAccessPolicies: ModuleResourceFileAccessPolicy[];
36
+ moduleResourceFileAccessPolicies: Map<
37
+ string,
38
+ ModuleResourceFileAccessPolicy[]
39
+ >;
40
+ resourceFileAccessPolicyKeys: Set<string>;
41
+ handlerKeys: Set<string>;
42
+ handlerInstallers: HandlerInstaller[];
43
+ durableHandlers: RegisteredModuleDurableHandler[];
44
+ routes: RegisteredModuleRoute[];
45
+ routeKeys: Set<string>;
46
+ taskHandlers: RegisteredModuleTaskHandler[];
47
+ taskKeys: Set<string>;
48
+ toolHandlers: RegisteredModuleToolHandler[];
49
+ sandboxToolHandlers: RegisteredModuleSandboxToolHandler[];
50
+ toolKeys: Set<string>;
51
+ aiOperationHandlers: RegisteredModuleAiOperationHandler[];
52
+ aiOperationKeys: Set<string>;
53
+ governedResources: RegisteredModuleGovernedResource[];
54
+ governedResourceKeys: Set<string>;
55
+ rolePresets: RegisteredModuleRolePreset[];
56
+ rolePresetKeys: Set<string>;
57
+ searchProviders: RegisteredModuleSearchProvider[];
58
+ searchProviderKeys: Set<string>;
59
+ debugSessionProviders: RegisteredModuleDebugSessionProvider[];
60
+ debugSessionProviderKeys: Set<string>;
61
+ }
62
+
63
+ export function createScopedHost(
64
+ manifest: ModuleManifest,
65
+ state: HostCollections,
66
+ ): ModuleHost {
67
+ const moduleName = manifest.name;
68
+ return {
69
+ moduleName,
70
+ registerUploadPolicy(policy) {
71
+ requireModuleNamespace("上传用途", policy.purpose, moduleName);
72
+ if (state.uploadPurposes.has(policy.purpose)) {
73
+ throw new Error(`上传用途重复注册:${policy.purpose}`);
74
+ }
75
+ state.uploadPurposes.add(policy.purpose);
76
+ state.uploadPolicies.push(policy);
77
+ const purposes =
78
+ state.modulePurposes.get(moduleName) ?? new Set<string>();
79
+ purposes.add(policy.purpose);
80
+ state.modulePurposes.set(moduleName, purposes);
81
+ },
82
+ registerResourceFileAccessPolicies(policies) {
83
+ registerResourceFileAccessPolicies(manifest, state, policies);
84
+ },
85
+ registerDurableHandler(definition) {
86
+ requireModuleNamespace("任务 handler", definition.key, moduleName);
87
+ registerModuleDurableHandler(manifest, state, definition);
88
+ },
89
+ registerGovernedResources(registrations) {
90
+ registerGovernedResources(manifest, state, registrations);
91
+ },
92
+ registerRoutes(routes) {
93
+ for (const definition of routes) {
94
+ requireModuleNamespace(
95
+ "路由权限",
96
+ definition.permissionKey,
97
+ moduleName,
98
+ );
99
+ validateRouteDeclaration(manifest, definition);
100
+ const key = `${definition.method} ${definition.path}`;
101
+ if (state.routeKeys.has(key)) {
102
+ throw new Error(`模块路由重复注册:${key}`);
103
+ }
104
+ state.routeKeys.add(key);
105
+ state.routes.push({ moduleName, definition });
106
+ }
107
+ },
108
+ registerTaskHandlers(registrations) {
109
+ for (const registration of registrations) {
110
+ requireModuleNamespace("定时任务", registration.taskKey, moduleName);
111
+ if (!manifest.tasks.some(({ key }) => key === registration.taskKey)) {
112
+ throw new Error(
113
+ `定时任务 Handler 未在 Manifest 声明:${registration.taskKey}`,
114
+ );
115
+ }
116
+ if (state.taskKeys.has(registration.taskKey)) {
117
+ throw new Error(`定时任务 Handler 重复注册:${registration.taskKey}`);
118
+ }
119
+ state.taskKeys.add(registration.taskKey);
120
+ state.taskHandlers.push({ moduleName, registration });
121
+ }
122
+ },
123
+ registerToolHandlers(registrations) {
124
+ for (const registration of registrations) {
125
+ requireModuleNamespace("Agent Tool", registration.toolKey, moduleName);
126
+ if (!manifest.tools.some(({ key }) => key === registration.toolKey)) {
127
+ throw new Error(
128
+ `Agent Tool Handler 未在 Manifest 声明:${registration.toolKey}`,
129
+ );
130
+ }
131
+ if (state.toolKeys.has(registration.toolKey)) {
132
+ throw new Error(
133
+ `Agent Tool Handler 重复注册:${registration.toolKey}`,
134
+ );
135
+ }
136
+ state.toolKeys.add(registration.toolKey);
137
+ state.toolHandlers.push({ moduleName, registration });
138
+ }
139
+ },
140
+ registerAiOperationHandlers(registrations) {
141
+ for (const registration of registrations) {
142
+ requireModuleNamespace(
143
+ "AI Operation",
144
+ registration.operationKey,
145
+ moduleName,
146
+ );
147
+ const operation = manifest.aiOperations?.find(
148
+ ({ key }) => key === registration.operationKey,
149
+ );
150
+ if (!operation) {
151
+ throw new Error(
152
+ `AI Operation Handler 未在 Manifest 声明:${registration.operationKey}`,
153
+ );
154
+ }
155
+ if (
156
+ (operation.kind === "agent" && !registration.prepare) ||
157
+ (operation.kind === "document" && !registration.prepareDocument) ||
158
+ (operation.kind === "speech" && !registration.prepareSpeech) ||
159
+ (operation.kind === "embedding" && !registration.prepareEmbedding)
160
+ ) {
161
+ throw new Error(
162
+ `AI Operation Handler 与 kind 不匹配:${registration.operationKey}`,
163
+ );
164
+ }
165
+ if (state.aiOperationKeys.has(registration.operationKey)) {
166
+ throw new Error(
167
+ `AI Operation Handler 重复注册:${registration.operationKey}`,
168
+ );
169
+ }
170
+ state.aiOperationKeys.add(registration.operationKey);
171
+ state.aiOperationHandlers.push({ moduleName, registration });
172
+ }
173
+ },
174
+ registerRolePresets(presets) {
175
+ for (const preset of presets) {
176
+ requireModuleNamespace("角色预设", preset.key, moduleName);
177
+ if (state.rolePresetKeys.has(preset.key)) {
178
+ throw new Error(`角色预设重复注册:${preset.key}`);
179
+ }
180
+ for (const permissionKey of preset.permissionKeys) {
181
+ requireModuleNamespace("角色预设权限", permissionKey, moduleName);
182
+ if (!manifest.permissions.some(({ key }) => key === permissionKey)) {
183
+ throw new Error(
184
+ `角色预设引用了模块 ${moduleName} 未声明权限:${permissionKey}`,
185
+ );
186
+ }
187
+ }
188
+ state.rolePresetKeys.add(preset.key);
189
+ state.rolePresets.push({ moduleName, preset });
190
+ }
191
+ },
192
+ registerSearchProviders(providers) {
193
+ for (const provider of providers) {
194
+ requireModuleNamespace("搜索 Provider", provider.key, moduleName);
195
+ validateSearchProviderRegistration(manifest, provider);
196
+ if (state.searchProviderKeys.has(provider.key)) {
197
+ throw new Error(`搜索 Provider 重复注册:${provider.key}`);
198
+ }
199
+ state.searchProviderKeys.add(provider.key);
200
+ state.searchProviders.push({ moduleName, provider });
201
+ }
202
+ },
203
+ registerDebugSessionProviders(providers) {
204
+ for (const provider of providers) {
205
+ validateDebugSessionProviderRegistration(manifest, provider);
206
+ if (state.debugSessionProviderKeys.has(provider.key)) {
207
+ throw new Error(`调试会话 Provider 重复注册:${provider.key}`);
208
+ }
209
+ state.debugSessionProviderKeys.add(provider.key);
210
+ state.debugSessionProviders.push({ moduleName, provider });
211
+ }
212
+ },
213
+ };
214
+ }
@@ -9,6 +9,7 @@ export {
9
9
  type RegisteredModuleTaskHandler,
10
10
  type RegisteredModuleToolHandler,
11
11
  type RegisteredModuleAiOperationHandler,
12
+ type RegisteredModuleDebugSessionProvider,
12
13
  } from "./module-host/initialize.js";
13
14
  export {
14
15
  buildModuleRequestContext,
@@ -7,7 +7,7 @@ import { createAdkDebugRuntime } from "./runtime-adk-debug.js";
7
7
  const token = "debug-token-012345678901234567890123";
8
8
 
9
9
  describe("ADK Web 专用调试身份", () => {
10
- test("只在 canonical Run 路径委托真实用户并收窄权限", async () => {
10
+ test("只在 canonical Run 与只读 debug-session 路径委托真实用户", async () => {
11
11
  const runtime = createRuntime();
12
12
  expect(
13
13
  runtime.claimsRequest(request("/api/foundation", "GET", token)),
@@ -28,6 +28,30 @@ describe("ADK Web 专用调试身份", () => {
28
28
  expect(
29
29
  await runtime.resolveRequest(request("/api/foundation", "GET", token)),
30
30
  ).toBeUndefined();
31
+ expect(
32
+ await runtime.resolveRequest(
33
+ request("/api/agent/debug/sessions/v1", "GET", token),
34
+ ),
35
+ ).toMatchObject({ id: "service_adk_debug" });
36
+ expect(
37
+ await runtime.resolveRequest(
38
+ request(
39
+ "/api/agent/debug/sessions/v1/wdbg1_" + "a".repeat(43),
40
+ "GET",
41
+ token,
42
+ ),
43
+ ),
44
+ ).toMatchObject({ id: "service_adk_debug" });
45
+ expect(
46
+ await runtime.resolveRequest(
47
+ request("/api/agent/debug/sessions/v1", "POST", token),
48
+ ),
49
+ ).toBeUndefined();
50
+ expect(
51
+ await runtime.resolveRequest(
52
+ request("/api/agent/debug/sessions/v1/one/events", "GET", token),
53
+ ),
54
+ ).toBeUndefined();
31
55
  expect(
32
56
  await runtime.resolveRequest(
33
57
  request("/api/agent/runs/run_1/result", "GET"),
@@ -45,7 +45,7 @@ export function createAdkDebugRuntime(
45
45
  return {
46
46
  claimsRequest,
47
47
  async resolveRequest(request: Request): Promise<RequestActor | undefined> {
48
- if (!claimsRequest(request) || !isCanonicalRunRequest(request)) {
48
+ if (!claimsRequest(request) || !isAdkDebugRequest(request)) {
49
49
  return undefined;
50
50
  }
51
51
  return resolveDelegatedActor(actorResolver, dataActorId, permissionKeys);
@@ -123,8 +123,14 @@ function resolvePermissionClosure(
123
123
  return [...new Set([operation.permissionKey, ...toolPermissions])];
124
124
  }
125
125
 
126
- function isCanonicalRunRequest(request: Request): boolean {
126
+ function isAdkDebugRequest(request: Request): boolean {
127
127
  const path = new URL(request.url).pathname.replace(/\/+$/, "");
128
+ if (
129
+ request.method === "GET" &&
130
+ /^\/api\/agent\/debug\/sessions\/v1(?:\/[^/]+)?$/.test(path)
131
+ ) {
132
+ return true;
133
+ }
128
134
  if (request.method === "POST" && path === "/api/agent/runs") return true;
129
135
  const match = path.match(
130
136
  /^\/api\/agent\/runs\/[^/]+\/(events|result|cancel|attach)$/,
@@ -42,6 +42,8 @@ describePostgres("PostgreSQL watermark policy repository", () => {
42
42
  density: "high",
43
43
  contentFields: ["username", "phone"],
44
44
  customContent: "内部资料",
45
+ opacityPercent: 16,
46
+ fontSize: 20,
45
47
  },
46
48
  "user_settings",
47
49
  );
@@ -56,6 +58,8 @@ describePostgres("PostgreSQL watermark policy repository", () => {
56
58
  density: "high",
57
59
  contentFields: ["username", "phone"],
58
60
  customContent: "内部资料",
61
+ opacityPercent: 16,
62
+ fontSize: 20,
59
63
  });
60
64
  });
61
65
  });
@@ -30,6 +30,7 @@ export class DrizzleWatermarkPolicyRepository implements WatermarkPolicyReposito
30
30
  .values({
31
31
  id: PLATFORM_WATERMARK_ID,
32
32
  ...policy,
33
+ businessPagesEnabled: policy.enabled,
33
34
  createdBy: actorId,
34
35
  updatedBy: actorId,
35
36
  })
@@ -37,10 +38,12 @@ export class DrizzleWatermarkPolicyRepository implements WatermarkPolicyReposito
37
38
  target: platformWatermarkPolicies.id,
38
39
  set: {
39
40
  enabled: policy.enabled,
40
- businessPagesEnabled: policy.businessPagesEnabled,
41
+ businessPagesEnabled: policy.enabled,
41
42
  density: policy.density,
42
43
  contentFields: policy.contentFields,
43
44
  customContent: policy.customContent,
45
+ opacityPercent: policy.opacityPercent,
46
+ fontSize: policy.fontSize,
44
47
  updatedAt: new Date(),
45
48
  updatedBy: actorId,
46
49
  },
@@ -57,12 +60,17 @@ function toPolicy(row: {
57
60
  density: PlatformWatermarkPolicy["density"];
58
61
  contentFields: PlatformWatermarkPolicy["contentFields"];
59
62
  customContent: string;
63
+ opacityPercent: number;
64
+ fontSize: number;
60
65
  }): PlatformWatermarkPolicy {
66
+ const enabled = row.enabled && row.businessPagesEnabled;
61
67
  return {
62
- enabled: row.enabled,
63
- businessPagesEnabled: row.businessPagesEnabled,
68
+ enabled,
69
+ businessPagesEnabled: enabled,
64
70
  density: row.density,
65
71
  contentFields: row.contentFields,
66
72
  customContent: row.customContent,
73
+ opacityPercent: row.opacityPercent,
74
+ fontSize: row.fontSize,
67
75
  };
68
76
  }
@@ -47,6 +47,8 @@ describe("平台水印策略路由", () => {
47
47
  density: "medium",
48
48
  contentFields: ["username", "displayName"],
49
49
  customContent: "",
50
+ opacityPercent: 8,
51
+ fontSize: 14,
50
52
  });
51
53
  });
52
54
 
@@ -58,6 +60,8 @@ describe("平台水印策略路由", () => {
58
60
  density: "high",
59
61
  contentFields: ["username", "phone", "custom"],
60
62
  customContent: "内部资料",
63
+ opacityPercent: 16,
64
+ fontSize: 20,
61
65
  };
62
66
 
63
67
  const updated = (await fixture.app.invoke(
@@ -83,7 +87,13 @@ describe("平台水印策略路由", () => {
83
87
  action: "config.update",
84
88
  target: { type: "platform-watermark", id: "platform" },
85
89
  metadata: expect.objectContaining({
86
- changedFields: ["density", "contentFields", "customContent"],
90
+ changedFields: [
91
+ "density",
92
+ "contentFields",
93
+ "customContent",
94
+ "opacityPercent",
95
+ "fontSize",
96
+ ],
87
97
  }),
88
98
  }),
89
99
  ]),
@@ -104,6 +114,8 @@ describe("平台水印策略路由", () => {
104
114
  density: "low",
105
115
  contentFields: ["username"],
106
116
  customContent: "",
117
+ opacityPercent: 8,
118
+ fontSize: 14,
107
119
  })) as Response;
108
120
 
109
121
  expect(readable.enabled).toBe(true);
@@ -139,6 +151,8 @@ describe("平台水印策略路由", () => {
139
151
  density: "medium",
140
152
  contentFields: ["username"],
141
153
  customContent: "x".repeat(81),
154
+ opacityPercent: 8,
155
+ fontSize: 14,
142
156
  })) as Response;
143
157
 
144
158
  expect(response.status).toBe(400);
@@ -148,9 +162,48 @@ describe("平台水印策略路由", () => {
148
162
  density: "medium",
149
163
  contentFields: ["username", "displayName"],
150
164
  customContent: "",
165
+ opacityPercent: 8,
166
+ fontSize: 14,
151
167
  });
152
168
  expect(fixture.runtime.auditSink.list()).toHaveLength(0);
153
169
  });
170
+
171
+ test("旧业务范围状态会折叠到唯一开关", async () => {
172
+ const fixture = await createFixture(settingsActor(true));
173
+
174
+ const updated = (await update(fixture, {
175
+ enabled: true,
176
+ businessPagesEnabled: false,
177
+ density: "medium",
178
+ contentFields: ["email", "displayName"],
179
+ customContent: "",
180
+ opacityPercent: 8,
181
+ fontSize: 14,
182
+ })) as PlatformWatermarkPolicy;
183
+
184
+ expect(updated.enabled).toBe(true);
185
+ expect(updated.businessPagesEnabled).toBe(true);
186
+ expect(updated.contentFields).toEqual(["email", "displayName"]);
187
+ });
188
+
189
+ test("不透明度和字体大小超出范围时拒绝保存", async () => {
190
+ const fixture = await createFixture(settingsActor(true));
191
+
192
+ const response = (await update(fixture, {
193
+ enabled: true,
194
+ businessPagesEnabled: true,
195
+ density: "medium",
196
+ contentFields: ["username"],
197
+ customContent: "",
198
+ opacityPercent: 31,
199
+ fontSize: 14,
200
+ })) as Response;
201
+
202
+ expect(response.status).toBe(400);
203
+ await expect(response.json()).resolves.toMatchObject({
204
+ error: "INVALID_WATERMARK_POLICY",
205
+ });
206
+ });
154
207
  });
155
208
 
156
209
  async function update(
@@ -6,6 +6,8 @@ import type {
6
6
  import {
7
7
  WATERMARK_CONTENT_FIELDS,
8
8
  WATERMARK_DENSITIES,
9
+ WATERMARK_FONT_SIZE_RANGE,
10
+ WATERMARK_OPACITY_RANGE,
9
11
  } from "@southwind-ai/shared";
10
12
  import { evaluateRouteGuards, findFeature } from "../route-guards.js";
11
13
  import type { createServerRuntime } from "../runtime.js";
@@ -137,6 +139,28 @@ function parseWatermarkPolicy(body: unknown): WatermarkParseResult {
137
139
  if (customContent.length > 80) {
138
140
  return { ok: false, message: "自定义内容不能超过 80 个字符" };
139
141
  }
142
+ if (
143
+ typeof source.opacityPercent !== "number" ||
144
+ !Number.isInteger(source.opacityPercent) ||
145
+ source.opacityPercent < WATERMARK_OPACITY_RANGE.min ||
146
+ source.opacityPercent > WATERMARK_OPACITY_RANGE.max
147
+ ) {
148
+ return {
149
+ ok: false,
150
+ message: `水印不透明度必须是 ${WATERMARK_OPACITY_RANGE.min}% 到 ${WATERMARK_OPACITY_RANGE.max}% 的整数`,
151
+ };
152
+ }
153
+ if (
154
+ typeof source.fontSize !== "number" ||
155
+ !Number.isInteger(source.fontSize) ||
156
+ source.fontSize < WATERMARK_FONT_SIZE_RANGE.min ||
157
+ source.fontSize > WATERMARK_FONT_SIZE_RANGE.max
158
+ ) {
159
+ return {
160
+ ok: false,
161
+ message: `水印字体大小必须是 ${WATERMARK_FONT_SIZE_RANGE.min}px 到 ${WATERMARK_FONT_SIZE_RANGE.max}px 的整数`,
162
+ };
163
+ }
140
164
  const contentFields = [
141
165
  ...new Set(
142
166
  source.contentFields as PlatformWatermarkPolicy["contentFields"],
@@ -146,10 +170,12 @@ function parseWatermarkPolicy(body: unknown): WatermarkParseResult {
146
170
  ok: true,
147
171
  value: {
148
172
  enabled: source.enabled,
149
- businessPagesEnabled: source.businessPagesEnabled,
173
+ businessPagesEnabled: source.enabled,
150
174
  density: source.density as PlatformWatermarkPolicy["density"],
151
175
  contentFields,
152
176
  customContent,
177
+ opacityPercent: source.opacityPercent,
178
+ fontSize: source.fontSize,
153
179
  },
154
180
  };
155
181
  }
@@ -164,6 +190,8 @@ function changedFields(
164
190
  "density",
165
191
  "contentFields",
166
192
  "customContent",
193
+ "opacityPercent",
194
+ "fontSize",
167
195
  ].filter(
168
196
  (field) =>
169
197
  JSON.stringify(before[field as keyof PlatformWatermarkPolicy]) !==
@@ -84,6 +84,37 @@ describe("system operation routes", () => {
84
84
  path: "/imports",
85
85
  usedBytes: 500,
86
86
  }),
87
+ cpuMonitorProbe: async () => ({
88
+ status: "available",
89
+ logicalCores: 16,
90
+ hostUsagePercent: 42.5,
91
+ processUsagePercent: 3.25,
92
+ loadAverage1m: 2.1,
93
+ }),
94
+ gpuMonitorProbe: async () => ({
95
+ status: "available",
96
+ provider: "NVIDIA",
97
+ message: "GPU 指标可用。",
98
+ devices: [
99
+ {
100
+ index: 0,
101
+ uuid: "GPU-abc",
102
+ name: "NVIDIA RTX 5000",
103
+ usagePercent: 75,
104
+ memoryUsedBytes: 8_192,
105
+ memoryTotalBytes: 32_768,
106
+ memoryUsagePercent: 25,
107
+ temperatureCelsius: 62,
108
+ processes: [
109
+ {
110
+ pid: 4321,
111
+ name: "python ai-service.py",
112
+ usedMemoryBytes: 7_168,
113
+ },
114
+ ],
115
+ },
116
+ ],
117
+ }),
87
118
  },
88
119
  );
89
120
  const guardContext = await runtime.createGuardContext(
@@ -104,6 +135,15 @@ describe("system operation routes", () => {
104
135
  totalBytes: number;
105
136
  usagePercent: number;
106
137
  };
138
+ cpu: {
139
+ logicalCores: number;
140
+ hostUsagePercent: number;
141
+ processUsagePercent: number;
142
+ };
143
+ };
144
+ gpuMonitor: {
145
+ status: string;
146
+ devices: Array<{ name: string; processes: Array<{ pid: number }> }>;
107
147
  };
108
148
  databaseMonitor: { engine: string; version: string };
109
149
  diskMonitor: {
@@ -118,6 +158,20 @@ describe("system operation routes", () => {
118
158
  expect(snapshot.serviceMonitor.status).toBe("running");
119
159
  expect(snapshot.serviceMonitor.startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
120
160
  expect(snapshot.serviceMonitor.memory.usagePercent).toBeGreaterThan(0);
161
+ expect(snapshot.serviceMonitor.cpu).toMatchObject({
162
+ logicalCores: 16,
163
+ hostUsagePercent: 42.5,
164
+ processUsagePercent: 3.25,
165
+ });
166
+ expect(snapshot.gpuMonitor).toMatchObject({
167
+ status: "available",
168
+ devices: [
169
+ {
170
+ name: "NVIDIA RTX 5000",
171
+ processes: [{ pid: 4321 }],
172
+ },
173
+ ],
174
+ });
121
175
  expect(snapshot.databaseMonitor).toMatchObject({
122
176
  engine: "PostgreSQL",
123
177
  version: "16.3",
@@ -18,6 +18,12 @@ import {
18
18
  type DirectoryUsageProbe,
19
19
  type HostDiskUsageProbe,
20
20
  } from "./operation-monitoring.js";
21
+ import {
22
+ createCpuMonitorProbe,
23
+ createGpuMonitorProbe,
24
+ type CpuMonitorProbe,
25
+ type GpuMonitorProbe,
26
+ } from "./system-resource-monitoring.js";
21
27
 
22
28
  type ServerRuntime = ReturnType<typeof createServerRuntime>;
23
29
 
@@ -31,6 +37,8 @@ export interface OperationRouteOptions {
31
37
  hostDiskUsageProbe?: HostDiskUsageProbe;
32
38
  systemUsageProbe?: DirectoryUsageProbe;
33
39
  importDataUsageProbe?: DirectoryUsageProbe;
40
+ cpuMonitorProbe?: CpuMonitorProbe;
41
+ gpuMonitorProbe?: GpuMonitorProbe;
34
42
  onlineUserProvider?: () => Promise<ActiveAuthSession[]>;
35
43
  }
36
44
 
@@ -69,6 +77,8 @@ export function registerSystemOperationRoutes(
69
77
  process.env.WINDY_IMPORT_DATA_PATH,
70
78
  ),
71
79
  );
80
+ const cpuMonitorProbe = options.cpuMonitorProbe ?? createCpuMonitorProbe();
81
+ const gpuMonitorProbe = options.gpuMonitorProbe ?? createGpuMonitorProbe();
72
82
  const onlineUserProvider = options.onlineUserProvider ?? (async () => []);
73
83
  app.get("/system/operations", async (rawContext) => {
74
84
  const { guardContext } = rawContext as OperationRouteContext;
@@ -87,6 +97,8 @@ export function registerSystemOperationRoutes(
87
97
  hostDiskUsageProbe,
88
98
  systemUsageProbe,
89
99
  importDataUsageProbe,
100
+ cpuMonitorProbe,
101
+ gpuMonitorProbe,
90
102
  onlineUserProvider,
91
103
  )
92
104
  : forbidden(decision.reason);
@@ -108,6 +120,8 @@ async function createOperationSnapshot(
108
120
  hostDiskUsageProbe: HostDiskUsageProbe,
109
121
  systemUsageProbe: DirectoryUsageProbe,
110
122
  importDataUsageProbe: DirectoryUsageProbe,
123
+ cpuMonitorProbe: CpuMonitorProbe,
124
+ gpuMonitorProbe: GpuMonitorProbe,
111
125
  onlineUserProvider: () => Promise<ActiveAuthSession[]>,
112
126
  ) {
113
127
  const [
@@ -116,6 +130,8 @@ async function createOperationSnapshot(
116
130
  hostDiskUsage,
117
131
  systemUsage,
118
132
  importDataUsage,
133
+ cpuMonitor,
134
+ gpuMonitor,
119
135
  activeSessions,
120
136
  ] = await Promise.all([
121
137
  redisHealthProbe(),
@@ -123,6 +139,8 @@ async function createOperationSnapshot(
123
139
  hostDiskUsageProbe(),
124
140
  systemUsageProbe(),
125
141
  importDataUsageProbe(),
142
+ cpuMonitorProbe(),
143
+ gpuMonitorProbe(),
126
144
  onlineUserProvider(),
127
145
  ]);
128
146
  return {
@@ -138,7 +156,11 @@ async function createOperationSnapshot(
138
156
  lastSeenAt: (session.lastSeenAt || session.issuedAt).toISOString(),
139
157
  expiresAt: session.expiresAt.toISOString(),
140
158
  })),
141
- serviceMonitor: serviceMonitorSnapshot(),
159
+ serviceMonitor: {
160
+ ...serviceMonitorSnapshot(),
161
+ cpu: cpuMonitor,
162
+ },
163
+ gpuMonitor,
142
164
  databaseMonitor,
143
165
  diskMonitor: diskMonitorSnapshot(
144
166
  hostDiskUsage,
@@ -33,6 +33,7 @@ describe("系统内置角色同步", () => {
33
33
  // @windy-module system.settings end
34
34
  expect(role?.permissionKeys).not.toContain("system.user.reveal");
35
35
  expect(role?.permissionKeys).not.toContain("system.user.export");
36
+ expect(role?.permissionKeys).not.toContain("system.ai-runtime.read");
36
37
  expect(role?.permissionKeys).toContain("custom.permission");
37
38
  // @windy-module system.settings begin
38
39
  expect(role?.menuKeys).toContain("system.settings");
@@ -54,6 +55,9 @@ describe("系统内置角色同步", () => {
54
55
  expect(
55
56
  (await repositories.roles.get("role_developer"))?.permissionKeys,
56
57
  ).not.toEqual(expect.arrayContaining(["system.user.reveal"]));
58
+ expect(
59
+ (await repositories.roles.get("role_developer"))?.permissionKeys,
60
+ ).not.toContain("system.ai-runtime.read");
57
61
 
58
62
  await repositories.roles.update(
59
63
  "role_developer",