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.
- package/dist/cli.js +292 -125
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
- package/template/apps/agent-server/adk_web_agents/services.py +25 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
- package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
- package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
- package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
- package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
- package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
- package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
- package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
- package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
- package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
- package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
- package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
- package/template/apps/agent-server/tests/test_report_store.py +286 -0
- package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
- package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
- package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
- package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
- package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
- package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
- package/template/apps/server/src/agent/debug-session-id.ts +37 -0
- package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
- package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
- package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
- package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
- package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
- package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
- package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
- package/template/apps/server/src/agent/run-authorization.ts +32 -1
- package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +5 -0
- package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
- package/template/apps/server/src/agent/run-facade.test.ts +78 -0
- package/template/apps/server/src/agent/run-facade.ts +33 -3
- package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
- package/template/apps/server/src/agent/run-input-verification.ts +41 -0
- package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
- package/template/apps/server/src/agent/tool-host.test.ts +1 -0
- package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
- package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
- package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
- package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
- package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
- package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
- package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
- package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
- package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
- package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
- package/template/apps/server/src/license/runtime-service.test.ts +6 -0
- package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
- package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
- package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
- package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
- package/template/apps/server/src/module-host/initialize.ts +6 -197
- package/template/apps/server/src/module-host/request-context.ts +1 -0
- package/template/apps/server/src/module-host/scoped-host.ts +214 -0
- package/template/apps/server/src/module-host.ts +1 -0
- package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
- package/template/apps/server/src/runtime-adk-debug.ts +8 -2
- package/template/apps/server/src/system/operations.test.ts +54 -0
- package/template/apps/server/src/system/operations.ts +23 -1
- package/template/apps/server/src/system/seed.test.ts +4 -0
- package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
- package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
- package/template/apps/web/src/layout/AppSidebar.vue +6 -1
- package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
- package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
- package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
- package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
- package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
- package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
- package/template/apps/web/src/router/error-navigation.ts +22 -0
- package/template/apps/web/src/router/index.ts +12 -23
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
- package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
- package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
- package/template/apps/web/src/services/operations-api.ts +28 -0
- package/template/docker-compose.agent-debug.yml +1 -1
- package/template/docs/architecture/ai-runtime.md +2 -1
- package/template/docs/architecture/module-host.md +3 -0
- package/template/docs/platform/agent-run-attestation.md +65 -0
- package/template/docs/platform/agent-runtime-observability.md +135 -0
- package/template/docs/platform/agent-runtime.md +47 -3
- package/template/docs/platform/operations-overview.md +13 -0
- package/template/docs/platform/platform-authentication.md +4 -3
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
- package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
- package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
- package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
- package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
- package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
- package/template/packages/database/drizzle/meta/_journal.json +21 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
- package/template/packages/database/src/schema/agent-runtime.ts +114 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-admin-routes.ts +10 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
- package/template/packages/modules/src/system-api-control-plane.ts +17 -1
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +8 -0
- package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
- package/template/packages/modules/src/system-module-catalog.ts +15 -4
- package/template/packages/modules/src/system-modules-access.test.ts +69 -0
- package/template/packages/modules/src/system-modules.ts +13 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
- package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
- package/template/packages/server-sdk/src/module-host.ts +5 -0
- package/template/packages/shared/index.ts +3 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/ai-runtime-report.ts +134 -0
- package/template/scripts/lib/postgres-regression.ts +201 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { createTestRunAuthorizationStore } from "./run-authorization.test-support.js";
|
|
8
8
|
import { debugActorAuditMetadata } from "./run-debug-authorization.js";
|
|
9
9
|
import { bindModuleUserProjection } from "../guards.js";
|
|
10
|
+
import { buildScopeContext } from "../data-access/context.js";
|
|
10
11
|
|
|
11
12
|
describe("ADK 调试 Operation 边界", () => {
|
|
12
13
|
test("专用身份不能切换到未选定的 Agent Operation", async () => {
|
|
@@ -53,4 +54,42 @@ describe("ADK 调试 Operation 边界", () => {
|
|
|
53
54
|
),
|
|
54
55
|
).toEqual({ debugDataActorId: "user_dev_admin" });
|
|
55
56
|
});
|
|
57
|
+
|
|
58
|
+
test("报表按 Module Actor 投影归属真实调试用户", async () => {
|
|
59
|
+
const fixture = await createRunFacadeFixture();
|
|
60
|
+
const actor = bindModuleUserProjection(
|
|
61
|
+
{
|
|
62
|
+
...fixture.context.actor,
|
|
63
|
+
id: "service_adk_debug",
|
|
64
|
+
type: "service",
|
|
65
|
+
scopeContext: buildScopeContext("user_dev_admin", "dept_platform", [
|
|
66
|
+
{
|
|
67
|
+
id: "role-debug",
|
|
68
|
+
code: "debug",
|
|
69
|
+
dataScope: "self",
|
|
70
|
+
departmentIds: [],
|
|
71
|
+
},
|
|
72
|
+
]),
|
|
73
|
+
},
|
|
74
|
+
"user_dev_admin",
|
|
75
|
+
);
|
|
76
|
+
const context = { ...fixture.context, actor };
|
|
77
|
+
Object.assign(fixture.runtime, {
|
|
78
|
+
agentDebugOperationForActor: (actorId: string) =>
|
|
79
|
+
actorId === "service_adk_debug" ? "business.answer" : undefined,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await fixture.facade.start(
|
|
83
|
+
"business.answer",
|
|
84
|
+
{ question: "调试归属" },
|
|
85
|
+
"request-debug-report",
|
|
86
|
+
context,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(fixture.remote.started[0]?.reporting).toEqual({
|
|
90
|
+
actorId: "user_dev_admin",
|
|
91
|
+
actorType: "user",
|
|
92
|
+
departmentId: "dept_platform",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
56
95
|
});
|
|
@@ -39,6 +39,10 @@ describe("Guarded Agent Run facade", () => {
|
|
|
39
39
|
promptVersion: "prompt-3",
|
|
40
40
|
routeVersion: "route-4",
|
|
41
41
|
},
|
|
42
|
+
reporting: {
|
|
43
|
+
actorId: "actor-1",
|
|
44
|
+
actorType: "user",
|
|
45
|
+
},
|
|
42
46
|
});
|
|
43
47
|
expect(fixture.remote.started[0]?.release.releaseId).toMatch(
|
|
44
48
|
/^release_[a-f0-9]{40}$/,
|
|
@@ -109,4 +113,78 @@ describe("Guarded Agent Run facade", () => {
|
|
|
109
113
|
message: "Agent Run 不存在或不可访问",
|
|
110
114
|
});
|
|
111
115
|
});
|
|
116
|
+
|
|
117
|
+
test("服务端可验证 canonical 输入,且同幂等键的不同输入 fail closed", async () => {
|
|
118
|
+
const fixture = await createRunFacadeFixture();
|
|
119
|
+
await fixture.facade.start(
|
|
120
|
+
"business.answer",
|
|
121
|
+
{ question: "原始问题", ignored: "不出网" },
|
|
122
|
+
"request-attested",
|
|
123
|
+
fixture.context,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await expect(
|
|
127
|
+
fixture.facade.verifyRunInput(
|
|
128
|
+
"business.answer",
|
|
129
|
+
"run_1",
|
|
130
|
+
{ question: "原始问题" },
|
|
131
|
+
fixture.context,
|
|
132
|
+
),
|
|
133
|
+
).resolves.toBeUndefined();
|
|
134
|
+
await expect(
|
|
135
|
+
fixture.facade.verifyRunInput(
|
|
136
|
+
"business.answer",
|
|
137
|
+
"run_1",
|
|
138
|
+
{ question: "伪造问题" },
|
|
139
|
+
fixture.context,
|
|
140
|
+
),
|
|
141
|
+
).rejects.toMatchObject({ code: "RUN_CONFLICT", status: 404 });
|
|
142
|
+
await expect(
|
|
143
|
+
fixture.facade.start(
|
|
144
|
+
"business.answer",
|
|
145
|
+
{ question: "伪造问题" },
|
|
146
|
+
"request-attested",
|
|
147
|
+
fixture.context,
|
|
148
|
+
),
|
|
149
|
+
).rejects.toMatchObject({ message: "Agent Run 不存在或不可访问" });
|
|
150
|
+
expect(fixture.remote.started).toHaveLength(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("敏感原文使用 DLP 前承诺且不会与脱敏占位符碰撞", async () => {
|
|
154
|
+
const fixture = await createRunFacadeFixture();
|
|
155
|
+
await fixture.facade.start(
|
|
156
|
+
"business.answer",
|
|
157
|
+
{ question: "联系 13800138000" },
|
|
158
|
+
"request-sensitive",
|
|
159
|
+
fixture.context,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(fixture.remote.started[0]?.input).toEqual({
|
|
163
|
+
question: "联系 [REDACTED]",
|
|
164
|
+
});
|
|
165
|
+
await expect(
|
|
166
|
+
fixture.facade.verifyRunInput(
|
|
167
|
+
"business.answer",
|
|
168
|
+
"run_1",
|
|
169
|
+
{ question: "联系 13800138000" },
|
|
170
|
+
fixture.context,
|
|
171
|
+
),
|
|
172
|
+
).resolves.toBeUndefined();
|
|
173
|
+
await expect(
|
|
174
|
+
fixture.facade.verifyRunInput(
|
|
175
|
+
"business.answer",
|
|
176
|
+
"run_1",
|
|
177
|
+
{ question: "联系 [REDACTED]" },
|
|
178
|
+
fixture.context,
|
|
179
|
+
),
|
|
180
|
+
).rejects.toMatchObject({ code: "RUN_CONFLICT", status: 404 });
|
|
181
|
+
await expect(
|
|
182
|
+
fixture.facade.start(
|
|
183
|
+
"business.answer",
|
|
184
|
+
{ question: "联系 [REDACTED]" },
|
|
185
|
+
"request-sensitive",
|
|
186
|
+
fixture.context,
|
|
187
|
+
),
|
|
188
|
+
).rejects.toMatchObject({ message: "Agent Run 不存在或不可访问" });
|
|
189
|
+
});
|
|
112
190
|
});
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
import { AgentRuntimeError } from "./run-contracts.js";
|
|
28
28
|
import {
|
|
29
29
|
governProviderInput,
|
|
30
|
+
projectProviderInput,
|
|
30
31
|
ProviderInputGovernanceError,
|
|
31
32
|
} from "./provider-input-governance.js";
|
|
32
33
|
import { rotateVisionArtifacts, validateOutputSchema } from "./vision-input.js";
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
} from "./run-definition-support.js";
|
|
39
40
|
import { AgentRunFacadeError, inaccessibleRun } from "./run-errors.js";
|
|
40
41
|
import { scopedRuntimeIdempotencyKey } from "./idempotency-key.js";
|
|
42
|
+
import { verifyCommittedRunInput } from "./run-input-verification.js";
|
|
41
43
|
import {
|
|
42
44
|
assertSelectedDebugOperation,
|
|
43
45
|
debugActorAuditMetadata,
|
|
@@ -52,7 +54,6 @@ type ServerRuntime = ReturnType<typeof createServerRuntime>;
|
|
|
52
54
|
|
|
53
55
|
export class AgentRunFacade {
|
|
54
56
|
readonly #operations: Map<string, RegisteredOperation>;
|
|
55
|
-
|
|
56
57
|
constructor(
|
|
57
58
|
modules: readonly ModuleManifest[],
|
|
58
59
|
hosts: InitializedModuleHosts,
|
|
@@ -63,7 +64,6 @@ export class AgentRunFacade {
|
|
|
63
64
|
) {
|
|
64
65
|
this.#operations = buildRegisteredOperations(modules, hosts);
|
|
65
66
|
}
|
|
66
|
-
|
|
67
67
|
async start(
|
|
68
68
|
operationKey: string,
|
|
69
69
|
input: ModuleAiJsonValue,
|
|
@@ -116,7 +116,11 @@ export class AgentRunFacade {
|
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
let governedInput = input;
|
|
119
|
+
let committedInput = input;
|
|
119
120
|
try {
|
|
121
|
+
committedInput = providerPolicy
|
|
122
|
+
? projectProviderInput(input, providerPolicy)
|
|
123
|
+
: input;
|
|
120
124
|
governedInput = providerPolicy
|
|
121
125
|
? governProviderInput(input, providerPolicy)
|
|
122
126
|
: input;
|
|
@@ -130,8 +134,9 @@ export class AgentRunFacade {
|
|
|
130
134
|
}
|
|
131
135
|
throw error;
|
|
132
136
|
}
|
|
137
|
+
const moduleActor = toModuleActor(context.actor);
|
|
133
138
|
const plan = await operation.registration.prepare(governedInput, {
|
|
134
|
-
actor:
|
|
139
|
+
actor: moduleActor,
|
|
135
140
|
requestId: context.requestId,
|
|
136
141
|
});
|
|
137
142
|
if (!plan.instructions.trim() || !plan.userPrompt.trim()) {
|
|
@@ -161,6 +166,7 @@ export class AgentRunFacade {
|
|
|
161
166
|
operationKey,
|
|
162
167
|
releaseId: release.releaseId,
|
|
163
168
|
idempotencyKey,
|
|
169
|
+
input: committedInput,
|
|
164
170
|
authorizationSnapshot: {
|
|
165
171
|
permissionKey: operation.definition.permissionKey,
|
|
166
172
|
featureKey: operation.definition.featureKey,
|
|
@@ -194,6 +200,13 @@ export class AgentRunFacade {
|
|
|
194
200
|
executionGrant: authorization.token,
|
|
195
201
|
release,
|
|
196
202
|
traceId,
|
|
203
|
+
reporting: {
|
|
204
|
+
actorId: moduleActor.id,
|
|
205
|
+
actorType: moduleActor.type,
|
|
206
|
+
...(context.actor.scopeContext?.departmentId
|
|
207
|
+
? { departmentId: context.actor.scopeContext.departmentId }
|
|
208
|
+
: {}),
|
|
209
|
+
},
|
|
197
210
|
},
|
|
198
211
|
scopedRuntimeIdempotencyKey(
|
|
199
212
|
context.actor.id,
|
|
@@ -282,6 +295,23 @@ export class AgentRunFacade {
|
|
|
282
295
|
return operation;
|
|
283
296
|
}
|
|
284
297
|
|
|
298
|
+
async verifyRunInput(
|
|
299
|
+
operationKey: string,
|
|
300
|
+
runId: string,
|
|
301
|
+
input: ModuleAiJsonValue,
|
|
302
|
+
context: RequestGuardContext,
|
|
303
|
+
): Promise<void> {
|
|
304
|
+
const operation = await this.authorizeRun(operationKey, runId, context);
|
|
305
|
+
await verifyCommittedRunInput({
|
|
306
|
+
operationKey,
|
|
307
|
+
runId,
|
|
308
|
+
value: input,
|
|
309
|
+
policy: operation.definition.providerDataPolicy,
|
|
310
|
+
context,
|
|
311
|
+
authorizations: this.authorizations,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
285
315
|
async attach(
|
|
286
316
|
operationKey: string,
|
|
287
317
|
runId: string,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
|
|
3
|
+
|
|
4
|
+
export function commitAgentRunInput(
|
|
5
|
+
secret: string,
|
|
6
|
+
tokenHash: string,
|
|
7
|
+
input: ModuleAiJsonValue,
|
|
8
|
+
): string {
|
|
9
|
+
const digest = createHash("sha256")
|
|
10
|
+
.update(canonicalizeJson(input))
|
|
11
|
+
.digest("hex");
|
|
12
|
+
return createHmac("sha256", secret)
|
|
13
|
+
.update(`input\u0000${tokenHash}\u0000${digest}`)
|
|
14
|
+
.digest("hex");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function sameInputCommitment(left: string, right: string): boolean {
|
|
18
|
+
const leftBytes = Buffer.from(left, "hex");
|
|
19
|
+
const rightBytes = Buffer.from(right, "hex");
|
|
20
|
+
return (
|
|
21
|
+
leftBytes.byteLength === 32 &&
|
|
22
|
+
rightBytes.byteLength === 32 &&
|
|
23
|
+
timingSafeEqual(leftBytes, rightBytes)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function canonicalizeJson(value: ModuleAiJsonValue): string {
|
|
28
|
+
if (
|
|
29
|
+
value === null ||
|
|
30
|
+
typeof value === "string" ||
|
|
31
|
+
typeof value === "boolean"
|
|
32
|
+
) {
|
|
33
|
+
return JSON.stringify(value);
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === "number") {
|
|
36
|
+
if (!Number.isFinite(value))
|
|
37
|
+
throw new Error("Agent Run 输入包含非有限数值");
|
|
38
|
+
return JSON.stringify(value);
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return `[${value.map(canonicalizeJson).join(",")}]`;
|
|
42
|
+
}
|
|
43
|
+
return `{${Object.keys(value)
|
|
44
|
+
.sort()
|
|
45
|
+
.map(
|
|
46
|
+
(key) =>
|
|
47
|
+
`${JSON.stringify(key)}:${canonicalizeJson(value[key] as ModuleAiJsonValue)}`,
|
|
48
|
+
)
|
|
49
|
+
.join(",")}}`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AiProviderDataPolicy } from "@southwind-ai/modules";
|
|
2
|
+
import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
|
|
3
|
+
import type { RequestGuardContext } from "../guards.js";
|
|
4
|
+
import {
|
|
5
|
+
projectProviderInput,
|
|
6
|
+
ProviderInputGovernanceError,
|
|
7
|
+
} from "./provider-input-governance.js";
|
|
8
|
+
import {
|
|
9
|
+
AgentRunAuthorizationError,
|
|
10
|
+
type AgentRunAuthorizationStore,
|
|
11
|
+
} from "./run-authorization.js";
|
|
12
|
+
import { inaccessibleRun } from "./run-errors.js";
|
|
13
|
+
|
|
14
|
+
export async function verifyCommittedRunInput(input: {
|
|
15
|
+
readonly operationKey: string;
|
|
16
|
+
readonly runId: string;
|
|
17
|
+
readonly value: ModuleAiJsonValue;
|
|
18
|
+
readonly policy?: AiProviderDataPolicy;
|
|
19
|
+
readonly context: RequestGuardContext;
|
|
20
|
+
readonly authorizations: AgentRunAuthorizationStore;
|
|
21
|
+
}): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
const committed = input.policy
|
|
24
|
+
? projectProviderInput(input.value, input.policy)
|
|
25
|
+
: input.value;
|
|
26
|
+
await input.authorizations.verifyRunInput(
|
|
27
|
+
input.operationKey,
|
|
28
|
+
input.runId,
|
|
29
|
+
committed,
|
|
30
|
+
input.context,
|
|
31
|
+
);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (
|
|
34
|
+
error instanceof AgentRunAuthorizationError ||
|
|
35
|
+
error instanceof ProviderInputGovernanceError
|
|
36
|
+
) {
|
|
37
|
+
throw inaccessibleRun();
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { createFoundationSnapshot, loadSystemModules } from "../foundation.js";
|
|
3
|
+
import { createRequestGuardContext } from "../guards.js";
|
|
4
|
+
import { buildScopeContext } from "../data-access/context.js";
|
|
5
|
+
import { createServerRuntime } from "../runtime.js";
|
|
6
|
+
import { createSystemRepositories } from "../system/seed.js";
|
|
7
|
+
import { FakeRouteApp } from "../system/route-test-app.js";
|
|
8
|
+
import { registerAiRuntimeReporting } from "./runtime-bootstrap.js";
|
|
9
|
+
import { registerUnavailableAiRoutes } from "./unavailable-routes.js";
|
|
10
|
+
|
|
11
|
+
const originalFetch = globalThis.fetch;
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
globalThis.fetch = originalFetch;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("AI Runtime 报表生产组合", () => {
|
|
18
|
+
test("注册受权限保护的路由并复用 Agent Server 地址与内部 Token", async () => {
|
|
19
|
+
const snapshot = createFoundationSnapshot();
|
|
20
|
+
const modules = loadSystemModules();
|
|
21
|
+
const features = modules.flatMap(({ features }) => features);
|
|
22
|
+
const runtime = createServerRuntime(
|
|
23
|
+
{ WINDY_DEV_ADMIN_TOKEN: "admin-token" },
|
|
24
|
+
{ featureCatalog: features },
|
|
25
|
+
);
|
|
26
|
+
const app = new FakeRouteApp();
|
|
27
|
+
const remote = mock(
|
|
28
|
+
async (input: string | URL | Request, init?: RequestInit) => {
|
|
29
|
+
const url = new URL(
|
|
30
|
+
input instanceof Request ? input.url : input.toString(),
|
|
31
|
+
);
|
|
32
|
+
expect(url.origin).toBe("http://agent-report.test");
|
|
33
|
+
expect(url.pathname).toBe("/api/internal/ai-runtime-report/summary");
|
|
34
|
+
expect(url.searchParams.get("scopeMode")).toBe("all");
|
|
35
|
+
expect(new Headers(init?.headers).get("authorization")).toBe(
|
|
36
|
+
"Bearer internal-token-for-tests",
|
|
37
|
+
);
|
|
38
|
+
return Response.json(emptySummary());
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
globalThis.fetch = remote as unknown as typeof globalThis.fetch;
|
|
42
|
+
registerAiRuntimeReporting({
|
|
43
|
+
app,
|
|
44
|
+
features,
|
|
45
|
+
runtime,
|
|
46
|
+
systemRepositories: createSystemRepositories(snapshot),
|
|
47
|
+
agentServerUrl: "http://agent-report.test",
|
|
48
|
+
internalToken: "internal-token-for-tests",
|
|
49
|
+
});
|
|
50
|
+
const guardContext = await createRequestGuardContext(
|
|
51
|
+
new Request("http://server.test/api/system/ai-runtime/summary"),
|
|
52
|
+
{
|
|
53
|
+
resolveActor: () => ({
|
|
54
|
+
id: "report-admin",
|
|
55
|
+
type: "user",
|
|
56
|
+
name: "报表管理员",
|
|
57
|
+
roleCodes: ["report-admin"],
|
|
58
|
+
permissionKeys: ["system.ai-runtime.read"],
|
|
59
|
+
scopeContext: buildScopeContext("report-admin", "dept_platform", [
|
|
60
|
+
{
|
|
61
|
+
id: "role-report-admin",
|
|
62
|
+
code: "report-admin",
|
|
63
|
+
dataScope: "all",
|
|
64
|
+
departmentIds: [],
|
|
65
|
+
},
|
|
66
|
+
]),
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const result = await app.invoke("GET", "/system/ai-runtime/summary", {
|
|
72
|
+
guardContext,
|
|
73
|
+
params: {},
|
|
74
|
+
query: { range: "day" },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(result).toEqual(emptySummary());
|
|
78
|
+
expect(remote).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(runtime.auditSink.list().at(-1)?.action).toBe(
|
|
80
|
+
"agent.runtime.report.read",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("AI disabled 时四条报表接口明确返回 503", async () => {
|
|
85
|
+
const app = new FakeRouteApp();
|
|
86
|
+
registerUnavailableAiRoutes(app);
|
|
87
|
+
|
|
88
|
+
for (const path of [
|
|
89
|
+
"/system/ai-runtime/summary",
|
|
90
|
+
"/system/ai-runtime/runs",
|
|
91
|
+
"/system/ai-runtime/runs/:runId",
|
|
92
|
+
"/system/ai-runtime/actors",
|
|
93
|
+
]) {
|
|
94
|
+
const response = (await app.invoke("GET", path, {})) as Response;
|
|
95
|
+
expect(response.status).toBe(503);
|
|
96
|
+
await expect(response.json()).resolves.toEqual({
|
|
97
|
+
code: "RUNTIME_UNAVAILABLE",
|
|
98
|
+
message: "AI Runtime 未启用",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function emptySummary() {
|
|
105
|
+
return {
|
|
106
|
+
generatedAt: "2026-07-31T00:00:00Z",
|
|
107
|
+
reportingAvailableFrom: null,
|
|
108
|
+
retentionDays: 90,
|
|
109
|
+
runs: {
|
|
110
|
+
total: 0,
|
|
111
|
+
completed: 0,
|
|
112
|
+
failed: 0,
|
|
113
|
+
cancelled: 0,
|
|
114
|
+
inProgress: 0,
|
|
115
|
+
successRate: null,
|
|
116
|
+
},
|
|
117
|
+
cache: {
|
|
118
|
+
hit: 0,
|
|
119
|
+
miss: 0,
|
|
120
|
+
unknown: 0,
|
|
121
|
+
hitRate: null,
|
|
122
|
+
tokenRate: null,
|
|
123
|
+
coverageRate: 0,
|
|
124
|
+
},
|
|
125
|
+
tokens: {
|
|
126
|
+
input: 0,
|
|
127
|
+
output: 0,
|
|
128
|
+
cachedInput: 0,
|
|
129
|
+
reasoning: 0,
|
|
130
|
+
total: 0,
|
|
131
|
+
coverageRate: 0,
|
|
132
|
+
},
|
|
133
|
+
byModel: [],
|
|
134
|
+
activity: [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { PlatformConfig } from "@southwind-ai/config";
|
|
2
2
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
3
|
+
import type { FeatureFlagDefinition } from "@southwind-ai/shared";
|
|
4
|
+
import { SystemAiRuntimeActorDirectory } from "../ai-runtime-report/actor-directory.js";
|
|
5
|
+
import { RemoteAiRuntimeReportPort } from "../ai-runtime-report/remote-port.js";
|
|
6
|
+
import { registerAiRuntimeReportRoutes } from "../ai-runtime-report/routes.js";
|
|
7
|
+
import { AiRuntimeReportService } from "../ai-runtime-report/service.js";
|
|
8
|
+
import { departmentDescendantResolver } from "../data-access/department-tree.js";
|
|
3
9
|
import type { InitializedModuleHosts } from "../module-host.js";
|
|
4
10
|
import type { createServerRuntime } from "../runtime.js";
|
|
5
11
|
import type { RouteApp } from "../system/routes.js";
|
|
@@ -51,6 +57,9 @@ import {
|
|
|
51
57
|
createStaticRouteAuthorizer,
|
|
52
58
|
} from "../module-host/static-route-authorization.js";
|
|
53
59
|
import { registerUnavailableAiRoutes } from "./unavailable-routes.js";
|
|
60
|
+
import { DebugSessionFacadeV1 } from "./debug-session-facade.js";
|
|
61
|
+
import { DebugSessionIdCodecV1 } from "./debug-session-id.js";
|
|
62
|
+
import { registerDebugSessionRoutesV1 } from "./debug-session-routes.js";
|
|
54
63
|
|
|
55
64
|
export function registerAgentPlatformRuntime(input: {
|
|
56
65
|
app: RouteApp;
|
|
@@ -107,6 +116,21 @@ export function registerAgentPlatformRuntime(input: {
|
|
|
107
116
|
input.app,
|
|
108
117
|
routeAuthorizer,
|
|
109
118
|
);
|
|
119
|
+
const debugToken =
|
|
120
|
+
process.env.NODE_ENV === "development" &&
|
|
121
|
+
process.env.WINDY_ADK_DEBUG_OPERATION_KEY?.trim()
|
|
122
|
+
? process.env.WINDY_ADK_DEBUG_TOKEN?.trim()
|
|
123
|
+
: undefined;
|
|
124
|
+
if (debugToken) {
|
|
125
|
+
registerDebugSessionRoutesV1(
|
|
126
|
+
input.app,
|
|
127
|
+
new DebugSessionFacadeV1(
|
|
128
|
+
input.hosts,
|
|
129
|
+
input.runtime,
|
|
130
|
+
new DebugSessionIdCodecV1(debugToken),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
110
134
|
const internalToken = requiredEnvironment("AGENT_SERVER_INTERNAL_TOKEN");
|
|
111
135
|
const aiArtifacts = new AiArtifactRuntime(
|
|
112
136
|
new AiArtifactCodec(requiredEnvironment("AI_ARTIFACT_ENCRYPTION_SECRET")),
|
|
@@ -117,11 +141,21 @@ export function registerAgentPlatformRuntime(input: {
|
|
|
117
141
|
resolveAgentActor,
|
|
118
142
|
);
|
|
119
143
|
registerAiArtifactRoutes(aiRouteApp, aiArtifacts, internalToken);
|
|
144
|
+
const agentServerUrl =
|
|
145
|
+
process.env.AGENT_SERVER_URL || "http://agent-server:8080";
|
|
120
146
|
const remote = new RemoteAgentRuntime({
|
|
121
|
-
baseUrl:
|
|
147
|
+
baseUrl: agentServerUrl,
|
|
122
148
|
internalToken,
|
|
123
149
|
toolHostUrl: process.env.AGENT_TOOL_HOST_URL || "http://server:9747",
|
|
124
150
|
});
|
|
151
|
+
registerAiRuntimeReporting({
|
|
152
|
+
app: aiRouteApp,
|
|
153
|
+
features,
|
|
154
|
+
runtime: input.runtime,
|
|
155
|
+
systemRepositories: input.systemRepositories,
|
|
156
|
+
agentServerUrl,
|
|
157
|
+
internalToken,
|
|
158
|
+
});
|
|
125
159
|
const grants = new ExecutionGrantStore();
|
|
126
160
|
const runAuthorizations = new AgentRunAuthorizationStore(
|
|
127
161
|
input.db
|
|
@@ -221,6 +255,37 @@ export function registerAgentPlatformRuntime(input: {
|
|
|
221
255
|
return { toolHandlers, aiArtifacts, aiOperations };
|
|
222
256
|
}
|
|
223
257
|
|
|
258
|
+
export function registerAiRuntimeReporting(input: {
|
|
259
|
+
app: RouteApp;
|
|
260
|
+
features: FeatureFlagDefinition[];
|
|
261
|
+
runtime: ReturnType<typeof createServerRuntime>;
|
|
262
|
+
systemRepositories: SystemRepositories;
|
|
263
|
+
agentServerUrl: string;
|
|
264
|
+
internalToken: string;
|
|
265
|
+
}): void {
|
|
266
|
+
registerAiRuntimeReportRoutes(
|
|
267
|
+
input.app,
|
|
268
|
+
new AiRuntimeReportService(
|
|
269
|
+
new RemoteAiRuntimeReportPort({
|
|
270
|
+
baseUrl: input.agentServerUrl,
|
|
271
|
+
internalToken: input.internalToken,
|
|
272
|
+
}),
|
|
273
|
+
new SystemAiRuntimeActorDirectory(
|
|
274
|
+
input.systemRepositories,
|
|
275
|
+
input.internalToken,
|
|
276
|
+
),
|
|
277
|
+
{
|
|
278
|
+
descendants: departmentDescendantResolver(
|
|
279
|
+
input.systemRepositories.departments,
|
|
280
|
+
),
|
|
281
|
+
},
|
|
282
|
+
input.runtime,
|
|
283
|
+
),
|
|
284
|
+
input.runtime,
|
|
285
|
+
input.features,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
224
289
|
function requiredEnvironment(key: string): string {
|
|
225
290
|
const value = process.env[key]?.trim();
|
|
226
291
|
if (!value) throw new Error(`启用 AI Runtime 时缺少 ${key}`);
|
|
@@ -180,6 +180,7 @@ async function createFixture(
|
|
|
180
180
|
undefined,
|
|
181
181
|
).releaseId,
|
|
182
182
|
idempotencyKey: "tool-host-test",
|
|
183
|
+
input: { question: "tool host" },
|
|
183
184
|
authorizationSnapshot: {
|
|
184
185
|
permissionKey: currentOperation.permissionKey,
|
|
185
186
|
featureKey: currentOperation.featureKey,
|
|
@@ -18,6 +18,10 @@ const unavailableRoutes = [
|
|
|
18
18
|
["POST", "/agent/runs/:runId/cancel"],
|
|
19
19
|
["POST", "/agent/runs/:runId/attach"],
|
|
20
20
|
["POST", "/internal/agent/tools/execute"],
|
|
21
|
+
["GET", "/system/ai-runtime/summary"],
|
|
22
|
+
["GET", "/system/ai-runtime/runs"],
|
|
23
|
+
["GET", "/system/ai-runtime/runs/:runId"],
|
|
24
|
+
["GET", "/system/ai-runtime/actors"],
|
|
21
25
|
] as const;
|
|
22
26
|
|
|
23
27
|
export function registerUnavailableAiRoutes(app: RouteApp): void {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AiRuntimeActorPage,
|
|
3
|
+
AiRuntimeActorSummary,
|
|
4
|
+
} from "@southwind-ai/shared";
|
|
5
|
+
import {
|
|
6
|
+
DataAccessDeniedError,
|
|
7
|
+
DataScopedResourceService,
|
|
8
|
+
} from "../data-access/scoped-resource.js";
|
|
9
|
+
import { departmentDescendantResolver } from "../data-access/department-tree.js";
|
|
10
|
+
import type { RequestActor } from "../guards.js";
|
|
11
|
+
import type { ManagedUser } from "../system/entities.js";
|
|
12
|
+
import type { SystemRepositories } from "../system/seed.js";
|
|
13
|
+
import { AiRuntimeActorHandleCodec } from "./actor-handle.js";
|
|
14
|
+
import type { AiRuntimeActorDirectory } from "./contracts.js";
|
|
15
|
+
|
|
16
|
+
export class SystemAiRuntimeActorDirectory implements AiRuntimeActorDirectory {
|
|
17
|
+
readonly #access: DataScopedResourceService<ManagedUser>;
|
|
18
|
+
readonly #handles: AiRuntimeActorHandleCodec;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
repositories: Pick<SystemRepositories, "users" | "departments">,
|
|
22
|
+
handleSecret: string,
|
|
23
|
+
) {
|
|
24
|
+
this.#access = new DataScopedResourceService(
|
|
25
|
+
repositories.users,
|
|
26
|
+
departmentDescendantResolver(repositories.departments),
|
|
27
|
+
);
|
|
28
|
+
this.#handles = new AiRuntimeActorHandleCodec(handleSecret);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async list(
|
|
32
|
+
query: { q?: string; page: number; pageSize: number },
|
|
33
|
+
actor: RequestActor,
|
|
34
|
+
): Promise<AiRuntimeActorPage> {
|
|
35
|
+
const result = await this.#access.list(
|
|
36
|
+
{
|
|
37
|
+
...query,
|
|
38
|
+
status: "active",
|
|
39
|
+
sort: [{ field: "displayName", order: "asc" }],
|
|
40
|
+
},
|
|
41
|
+
["displayName", "username"],
|
|
42
|
+
actor,
|
|
43
|
+
);
|
|
44
|
+
return {
|
|
45
|
+
...result,
|
|
46
|
+
items: result.items.map((user) => this.#actorSummary(user)),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async resolve(
|
|
51
|
+
actorIds: readonly string[],
|
|
52
|
+
actor: RequestActor,
|
|
53
|
+
): Promise<Map<string, AiRuntimeActorSummary>> {
|
|
54
|
+
const result = new Map<string, AiRuntimeActorSummary>();
|
|
55
|
+
await Promise.all(
|
|
56
|
+
[...new Set(actorIds)].map(async (id) => {
|
|
57
|
+
try {
|
|
58
|
+
const user = await this.#access.get(id, actor);
|
|
59
|
+
if (user) result.set(id, this.#actorSummary(user));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof DataAccessDeniedError)) throw error;
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async resolveHandle(
|
|
69
|
+
handle: string,
|
|
70
|
+
actor: RequestActor,
|
|
71
|
+
): Promise<string | undefined> {
|
|
72
|
+
const actorId = this.#handles.verify(handle);
|
|
73
|
+
if (!actorId) return undefined;
|
|
74
|
+
try {
|
|
75
|
+
const user = await this.#access.get(actorId, actor);
|
|
76
|
+
return user?.status === "active" ? user.id : undefined;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof DataAccessDeniedError) return undefined;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#actorSummary(user: {
|
|
84
|
+
id: string;
|
|
85
|
+
username?: string;
|
|
86
|
+
displayName?: string;
|
|
87
|
+
}): AiRuntimeActorSummary {
|
|
88
|
+
return {
|
|
89
|
+
handle: this.#handles.issue(user.id),
|
|
90
|
+
displayName: user.displayName?.trim() || user.username || "未命名用户",
|
|
91
|
+
...(user.username ? { username: user.username } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|