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,177 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+
5
+ from southwind_agent_server.contracts import (
6
+ RunReportingContext,
7
+ StartRunRequest,
8
+ )
9
+ from southwind_agent_server.deterministic import DeterministicEngineAdapter
10
+ from southwind_agent_server.report_store import (
11
+ InMemoryAgentRuntimeReportStore,
12
+ )
13
+ from southwind_agent_server.service import AgentRunService
14
+ from southwind_agent_server.store import (
15
+ CanonicalEvent,
16
+ InMemoryRunStore,
17
+ RunRecord,
18
+ )
19
+
20
+
21
+ def request(text: str = "done") -> StartRunRequest:
22
+ return StartRunRequest(
23
+ operation_key="test.echo",
24
+ input={"text": text},
25
+ trace_id="trace_1",
26
+ reporting=RunReportingContext(
27
+ actor_id="actor_1",
28
+ actor_type="user",
29
+ ),
30
+ )
31
+
32
+
33
+ async def test_rewritten_event_terminal_drives_report_status() -> None:
34
+ canonical = EventLimitRunStore()
35
+ report = InMemoryAgentRuntimeReportStore()
36
+ service = AgentRunService(
37
+ DeterministicEngineAdapter(),
38
+ canonical,
39
+ report,
40
+ )
41
+
42
+ record = await service.start(request(), "event-limit")
43
+ final = await wait_for_terminal(service, record.run_id)
44
+ detail = await report.get_run(record.run_id)
45
+ await service.close()
46
+
47
+ assert final.status == "failed"
48
+ assert detail is not None
49
+ assert detail["status"] == "failed"
50
+ assert detail["safeErrorCode"] == "BUDGET_EXCEEDED"
51
+
52
+
53
+ async def test_output_limit_terminal_drives_report_status() -> None:
54
+ canonical = OutputLimitRunStore()
55
+ report = InMemoryAgentRuntimeReportStore()
56
+ service = AgentRunService(
57
+ DeterministicEngineAdapter(),
58
+ canonical,
59
+ report,
60
+ )
61
+
62
+ record = await service.start(request("too large"), "output-limit")
63
+ final = await wait_for_terminal(service, record.run_id)
64
+ detail = await report.get_run(record.run_id)
65
+ await service.close()
66
+
67
+ assert final.status == "failed"
68
+ assert detail is not None
69
+ assert detail["status"] == "failed"
70
+ assert detail["safeErrorCode"] == "BUDGET_EXCEEDED"
71
+
72
+
73
+ async def test_completion_wins_cancel_race_in_report() -> None:
74
+ canonical = CompleteWinsCancelRunStore()
75
+ report = InMemoryAgentRuntimeReportStore()
76
+ service = AgentRunService(
77
+ DeterministicEngineAdapter(),
78
+ canonical,
79
+ report,
80
+ )
81
+ invocation = request()
82
+ record = run_record(invocation, "race")
83
+ await canonical.create_or_get(record, invocation.model_dump(by_alias=True))
84
+ await report.ensure_run(record.run_id, invocation, 100.0)
85
+ await report.start_run(record.run_id, 101.0)
86
+
87
+ final = await service.cancel(record.run_id)
88
+ detail = await report.get_run(record.run_id)
89
+ await service.close()
90
+
91
+ assert final.status == "completed"
92
+ assert detail is not None
93
+ assert detail["status"] == "completed"
94
+
95
+
96
+ async def wait_for_terminal(
97
+ service: AgentRunService,
98
+ run_id: str,
99
+ ) -> RunRecord:
100
+ async def poll() -> RunRecord:
101
+ while True:
102
+ record = await service.get(run_id)
103
+ if record.terminal:
104
+ return record
105
+ await asyncio.sleep(0)
106
+
107
+ return await asyncio.wait_for(poll(), 1)
108
+
109
+
110
+ def run_record(invocation: StartRunRequest, suffix: str) -> RunRecord:
111
+ return RunRecord(
112
+ run_id=f"run_{suffix}",
113
+ operation_key=invocation.operation_key,
114
+ input=invocation.input,
115
+ idempotency_key=f"key_{suffix}",
116
+ fingerprint=f"fingerprint_{suffix}",
117
+ release=invocation.release.model_dump(by_alias=True),
118
+ trace_id=invocation.trace_id,
119
+ )
120
+
121
+
122
+ def failure_event(run_id: str, sequence: int) -> CanonicalEvent:
123
+ return {
124
+ "type": "run.failed",
125
+ "runId": run_id,
126
+ "sequence": sequence,
127
+ "error": {
128
+ "code": "BUDGET_EXCEEDED",
129
+ "message": "Agent 存储超过安全上限",
130
+ "requestId": f"request_{sequence}",
131
+ "retryable": False,
132
+ },
133
+ }
134
+
135
+
136
+ class EventLimitRunStore(InMemoryRunStore):
137
+ async def append(
138
+ self,
139
+ run_id: str,
140
+ event_type: str,
141
+ fields: dict[str, object] | None = None,
142
+ ) -> CanonicalEvent | None:
143
+ if event_type == "usage.reported":
144
+ record = await self.get(run_id)
145
+ assert record is not None
146
+ event = failure_event(run_id, len(record.events) + 1)
147
+ return await super().append(
148
+ run_id,
149
+ "run.failed",
150
+ {"error": event["error"]},
151
+ )
152
+ return await super().append(run_id, event_type, fields)
153
+
154
+
155
+ class OutputLimitRunStore(InMemoryRunStore):
156
+ async def complete(self, run_id: str, output: object) -> None:
157
+ del output
158
+ record = await self.get(run_id)
159
+ assert record is not None
160
+ event = failure_event(run_id, len(record.events) + 1)
161
+ await super().append(
162
+ run_id,
163
+ "run.failed",
164
+ {"error": event["error"]},
165
+ )
166
+
167
+
168
+ class CompleteWinsCancelRunStore(InMemoryRunStore):
169
+ async def append(
170
+ self,
171
+ run_id: str,
172
+ event_type: str,
173
+ fields: dict[str, object] | None = None,
174
+ ) -> CanonicalEvent | None:
175
+ if event_type == "run.cancelled":
176
+ await super().complete(run_id, {"winner": "complete"})
177
+ return await super().append(run_id, event_type, fields)
@@ -0,0 +1,269 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { ModuleManifest } from "@southwind-ai/modules";
3
+ import {
4
+ MODULE_DEBUG_SESSION_SCHEMA_VERSION,
5
+ type ModuleDebugSessionProviderV1,
6
+ } from "@southwind-ai/server-sdk";
7
+ import { featureKey } from "@southwind-ai/shared";
8
+ import {
9
+ bindModuleUserProjection,
10
+ createRequestGuardContext,
11
+ type RequestActor,
12
+ } from "../guards.js";
13
+ import type { InitializedModuleHosts } from "../module-host.js";
14
+ import { manifest } from "../module-host/test-fixtures.js";
15
+ import { createServerRuntime } from "../runtime.js";
16
+ import { DebugSessionFacadeV1 } from "./debug-session-facade.js";
17
+ import { DebugSessionIdCodecV1 } from "./debug-session-id.js";
18
+
19
+ const OPERATION_KEY = "known.copilot.global";
20
+ const SOURCE_ID = "conversation-sensitive-42";
21
+ const SECRET_FAILURE = `provider failed for ${SOURCE_ID}`;
22
+
23
+ describe("ADK 调试会话 Provider 故障隔离", () => {
24
+ test("一个 Provider 故障时 fail closed,避免把 partial 伪装成完整列表", async () => {
25
+ const failing = provider("known.failing-history", {
26
+ list: async () => {
27
+ throw new Error(SECRET_FAILURE);
28
+ },
29
+ get: async () => undefined,
30
+ });
31
+ const healthy = provider("known.healthy-history", {
32
+ list: async () => ({
33
+ sessions: [summary("conversation-healthy")],
34
+ truncated: false,
35
+ }),
36
+ get: async () => ({
37
+ ...summary("conversation-healthy"),
38
+ messages: [],
39
+ }),
40
+ });
41
+ const fixture = createFixture([failing, healthy]);
42
+
43
+ await expect(
44
+ fixture.facade.list(fixture.context, signal()),
45
+ ).rejects.toMatchObject({
46
+ code: "PROVIDER_UNAVAILABLE",
47
+ status: 503,
48
+ });
49
+ const failure = fixture.runtime.auditSink
50
+ .list()
51
+ .find(({ outcome }) => outcome === "failure");
52
+ expect(failure).toMatchObject({
53
+ action: "known.copilot.history.read",
54
+ outcome: "failure",
55
+ target: {
56
+ type: "agent-debug-session-provider",
57
+ id: "known.failing-history",
58
+ },
59
+ metadata: {
60
+ debugDataActorId: "user_dev_admin",
61
+ mode: "list",
62
+ phase: "list",
63
+ failureCode: "PROVIDER_UNAVAILABLE",
64
+ },
65
+ });
66
+ expect(JSON.stringify(failure)).not.toContain(SECRET_FAILURE);
67
+ });
68
+
69
+ test("存在 Provider 且全部失败时返回 503", async () => {
70
+ const fixture = createFixture([
71
+ failingProvider("known.first-history"),
72
+ failingProvider("known.second-history"),
73
+ ]);
74
+
75
+ await expect(
76
+ fixture.facade.list(fixture.context, signal()),
77
+ ).rejects.toMatchObject({
78
+ code: "PROVIDER_UNAVAILABLE",
79
+ status: 503,
80
+ });
81
+ expect(
82
+ fixture.runtime.auditSink
83
+ .list()
84
+ .filter(({ outcome }) => outcome === "failure"),
85
+ ).toHaveLength(2);
86
+ });
87
+
88
+ test("详情读取遇到 Provider 故障且最终未命中时返回 503", async () => {
89
+ let failDetail = false;
90
+ const unstable = provider("known.unstable-history", {
91
+ list: async () => ({
92
+ sessions: [summary(SOURCE_ID)],
93
+ truncated: false,
94
+ }),
95
+ get: async () => {
96
+ if (failDetail) throw new Error(SECRET_FAILURE);
97
+ return { ...summary(SOURCE_ID), messages: [] };
98
+ },
99
+ });
100
+ const empty = provider("known.empty-history", {
101
+ list: async () => ({ sessions: [], truncated: false }),
102
+ get: async () => undefined,
103
+ });
104
+ const fixture = createFixture([unstable, empty]);
105
+ const listed = await fixture.facade.list(fixture.context, signal());
106
+ failDetail = true;
107
+
108
+ await expect(
109
+ fixture.facade.get(
110
+ listed.sessions[0]?.id ?? "",
111
+ fixture.context,
112
+ signal(),
113
+ ),
114
+ ).rejects.toMatchObject({
115
+ code: "PROVIDER_UNAVAILABLE",
116
+ status: 503,
117
+ });
118
+ const failure = fixture.runtime.auditSink
119
+ .list()
120
+ .find(
121
+ ({ outcome, metadata }) =>
122
+ outcome === "failure" && metadata?.phase === "get",
123
+ );
124
+ expect(failure?.metadata).toMatchObject({
125
+ mode: "get",
126
+ phase: "get",
127
+ failureCode: "PROVIDER_UNAVAILABLE",
128
+ });
129
+ expect(JSON.stringify(failure)).not.toContain(SECRET_FAILURE);
130
+ });
131
+ });
132
+
133
+ function failingProvider(key: string): ModuleDebugSessionProviderV1 {
134
+ return provider(key, {
135
+ list: async () => {
136
+ throw new Error(SECRET_FAILURE);
137
+ },
138
+ get: async () => undefined,
139
+ });
140
+ }
141
+
142
+ function provider(
143
+ key: string,
144
+ methods: Pick<ModuleDebugSessionProviderV1, "list" | "get">,
145
+ ): ModuleDebugSessionProviderV1 {
146
+ return {
147
+ schemaVersion: MODULE_DEBUG_SESSION_SCHEMA_VERSION,
148
+ key,
149
+ operationKeys: [OPERATION_KEY],
150
+ featureKey: "known.copilot",
151
+ permissionKey: "known.copilot.execute",
152
+ auditAction: "known.copilot.history.read",
153
+ ...methods,
154
+ };
155
+ }
156
+
157
+ function createFixture(providers: readonly ModuleDebugSessionProviderV1[]) {
158
+ const module = debugManifest();
159
+ const hosts = {
160
+ debugSessionProviders: providers.map((provider) => ({
161
+ moduleName: "known",
162
+ provider,
163
+ })),
164
+ manifestOf(moduleName: string) {
165
+ if (moduleName !== "known") throw new Error("unknown module");
166
+ return module;
167
+ },
168
+ } as unknown as InitializedModuleHosts;
169
+ const base = createServerRuntime(
170
+ {},
171
+ {
172
+ featureCatalog: module.features,
173
+ licenseResolver: { resolve: () => ({ status: "valid" }) },
174
+ },
175
+ );
176
+ const runtime = {
177
+ ...base,
178
+ agentDebugOperationForActor: (actorId: string) =>
179
+ actorId === "service_adk_debug" ? OPERATION_KEY : undefined,
180
+ };
181
+ const context = createRequestGuardContext(
182
+ new Request("http://localhost/api/agent/debug/sessions/v1"),
183
+ {
184
+ resolveActor: () => bindModuleUserProjection(actor(), "user_dev_admin"),
185
+ resolveLicense: () => ({ status: "valid" }),
186
+ licenseEnforced: false,
187
+ },
188
+ );
189
+ return {
190
+ context,
191
+ facade: new DebugSessionFacadeV1(
192
+ hosts,
193
+ runtime,
194
+ new DebugSessionIdCodecV1("test-secret-012345678901234567890123456789"),
195
+ ),
196
+ runtime,
197
+ };
198
+ }
199
+
200
+ function actor(): RequestActor {
201
+ return {
202
+ id: "service_adk_debug",
203
+ type: "service",
204
+ name: "ADK Debug",
205
+ roleCodes: ["adk-debug"],
206
+ permissionKeys: ["known.copilot.execute"],
207
+ };
208
+ }
209
+
210
+ function summary(sourceId: string) {
211
+ return {
212
+ sourceId,
213
+ title: "历史研判",
214
+ scopeLabel: "测试范围",
215
+ updatedAt: "2026-07-31T01:00:01.000Z",
216
+ revision: "2",
217
+ };
218
+ }
219
+
220
+ function signal(): AbortSignal {
221
+ return new AbortController().signal;
222
+ }
223
+
224
+ function debugManifest(): ModuleManifest {
225
+ return manifest("known", {
226
+ features: [
227
+ {
228
+ key: featureKey("known.copilot"),
229
+ label: "Copilot",
230
+ module: "known",
231
+ visible: "visible",
232
+ enabled: true,
233
+ stage: "stable",
234
+ },
235
+ ],
236
+ permissions: [
237
+ {
238
+ key: "known.copilot.execute",
239
+ label: "使用 Copilot",
240
+ module: "known",
241
+ moduleLabel: "Known",
242
+ resource: "copilot",
243
+ resourceLabel: "Copilot",
244
+ action: "execute",
245
+ },
246
+ ],
247
+ aiOperations: [
248
+ {
249
+ key: OPERATION_KEY,
250
+ kind: "agent",
251
+ featureKey: "known.copilot",
252
+ permissionKey: "known.copilot.execute",
253
+ auditAction: "known.copilot.execute",
254
+ inputSchemaKey: "known.copilot.input.v1",
255
+ outputSchemaKey: "known.copilot.output.v1",
256
+ requiredCapabilities: ["text-generation"],
257
+ allowedToolKeys: [],
258
+ dataClassification: "internal",
259
+ execution: "streaming",
260
+ limits: {
261
+ maxDurationSeconds: 60,
262
+ maxInputBytes: 4096,
263
+ maxOutputBytes: 4096,
264
+ },
265
+ },
266
+ ],
267
+ auditActions: ["known.copilot.execute", "known.copilot.history.read"],
268
+ });
269
+ }