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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleActor,
|
|
3
|
+
ModuleDebugSessionProviderV1,
|
|
4
|
+
ModuleDebugSessionSummaryV1,
|
|
5
|
+
} from "@southwind-ai/server-sdk";
|
|
6
|
+
import {
|
|
7
|
+
DebugSessionProviderContractError,
|
|
8
|
+
validateDebugSessionPage,
|
|
9
|
+
} from "./debug-session-validation.js";
|
|
10
|
+
|
|
11
|
+
const PAGE_SIZE = 100;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Drain one Provider with bounded keyset pages. The native ADK session API has
|
|
15
|
+
* no pagination surface, so returning a partial page would make older sessions
|
|
16
|
+
* permanently undiscoverable.
|
|
17
|
+
*/
|
|
18
|
+
export async function readAllDebugSessionSummaries(input: {
|
|
19
|
+
readonly provider: ModuleDebugSessionProviderV1;
|
|
20
|
+
readonly actor: ModuleActor;
|
|
21
|
+
readonly operationKey: string;
|
|
22
|
+
readonly signal: AbortSignal;
|
|
23
|
+
}): Promise<readonly ModuleDebugSessionSummaryV1[]> {
|
|
24
|
+
const sessions: ModuleDebugSessionSummaryV1[] = [];
|
|
25
|
+
const sourceIds = new Set<string>();
|
|
26
|
+
const cursors = new Set<string>();
|
|
27
|
+
let cursor: string | undefined;
|
|
28
|
+
for (;;) {
|
|
29
|
+
input.signal.throwIfAborted();
|
|
30
|
+
const page = validateDebugSessionPage(
|
|
31
|
+
await input.provider.list({
|
|
32
|
+
actor: input.actor,
|
|
33
|
+
operationKey: input.operationKey,
|
|
34
|
+
limit: PAGE_SIZE,
|
|
35
|
+
...(cursor ? { cursor } : {}),
|
|
36
|
+
signal: input.signal,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
for (const session of page.sessions) {
|
|
40
|
+
if (sourceIds.has(session.sourceId)) invalidProvider();
|
|
41
|
+
sourceIds.add(session.sourceId);
|
|
42
|
+
sessions.push(session);
|
|
43
|
+
}
|
|
44
|
+
if (!page.truncated) return sessions;
|
|
45
|
+
const next = page.next;
|
|
46
|
+
if (!next || cursors.has(next)) invalidProvider();
|
|
47
|
+
cursors.add(next);
|
|
48
|
+
cursor = next;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function invalidProvider(): never {
|
|
53
|
+
throw new DebugSessionProviderContractError("调试会话 Provider 分页游标无效");
|
|
54
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createRequestGuardContext } from "../guards.js";
|
|
3
|
+
import { FakeRouteApp } from "../system/route-test-app.js";
|
|
4
|
+
import {
|
|
5
|
+
DebugSessionFacadeError,
|
|
6
|
+
type DebugSessionFacadeV1,
|
|
7
|
+
} from "./debug-session-facade.js";
|
|
8
|
+
import { registerDebugSessionRoutesV1 } from "./debug-session-routes.js";
|
|
9
|
+
|
|
10
|
+
describe("ADK 调试会话 V1 routes", () => {
|
|
11
|
+
test("只注册 GET 并在错误响应复用 guard requestId", async () => {
|
|
12
|
+
const app = new FakeRouteApp();
|
|
13
|
+
registerDebugSessionRoutesV1(app, {
|
|
14
|
+
list: async () => {
|
|
15
|
+
throw new DebugSessionFacadeError(
|
|
16
|
+
"PROVIDER_UNAVAILABLE",
|
|
17
|
+
"暂不可用",
|
|
18
|
+
503,
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
} as unknown as DebugSessionFacadeV1);
|
|
22
|
+
const request = new Request("http://localhost/api/agent/debug/sessions/v1");
|
|
23
|
+
const guardContext = createRequestGuardContext(request);
|
|
24
|
+
|
|
25
|
+
const response = (await app.invoke("GET", "/agent/debug/sessions/v1", {
|
|
26
|
+
request,
|
|
27
|
+
guardContext,
|
|
28
|
+
params: {},
|
|
29
|
+
})) as Response;
|
|
30
|
+
|
|
31
|
+
expect([...app.handlers.keys()]).toEqual([
|
|
32
|
+
"GET /agent/debug/sessions/v1",
|
|
33
|
+
"GET /agent/debug/sessions/v1/:sessionId",
|
|
34
|
+
]);
|
|
35
|
+
expect(response.status).toBe(503);
|
|
36
|
+
expect(response.headers.get("cache-control")).toBe("no-store");
|
|
37
|
+
expect(await response.json()).toMatchObject({
|
|
38
|
+
code: "PROVIDER_UNAVAILABLE",
|
|
39
|
+
requestId: guardContext.requestId,
|
|
40
|
+
retryable: true,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("成功响应同样禁止缓存", async () => {
|
|
45
|
+
const app = new FakeRouteApp();
|
|
46
|
+
registerDebugSessionRoutesV1(app, {
|
|
47
|
+
list: async () => ({
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
sessions: [],
|
|
50
|
+
truncated: false,
|
|
51
|
+
}),
|
|
52
|
+
} as unknown as DebugSessionFacadeV1);
|
|
53
|
+
const request = new Request("http://localhost/api/agent/debug/sessions/v1");
|
|
54
|
+
|
|
55
|
+
const response = (await app.invoke("GET", "/agent/debug/sessions/v1", {
|
|
56
|
+
request,
|
|
57
|
+
guardContext: createRequestGuardContext(request),
|
|
58
|
+
params: {},
|
|
59
|
+
})) as Response;
|
|
60
|
+
|
|
61
|
+
expect(response.status).toBe(200);
|
|
62
|
+
expect(response.headers.get("cache-control")).toBe("no-store");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RequestGuardContext } from "../guards.js";
|
|
2
|
+
import type { RouteApp } from "../system/routes.js";
|
|
3
|
+
import {
|
|
4
|
+
DebugSessionFacadeError,
|
|
5
|
+
type DebugSessionFacadeV1,
|
|
6
|
+
} from "./debug-session-facade.js";
|
|
7
|
+
|
|
8
|
+
interface DebugSessionRouteContext {
|
|
9
|
+
params: Record<string, string | undefined>;
|
|
10
|
+
request: Request;
|
|
11
|
+
guardContext: RequestGuardContext;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerDebugSessionRoutesV1(
|
|
15
|
+
app: RouteApp,
|
|
16
|
+
facade: DebugSessionFacadeV1,
|
|
17
|
+
) {
|
|
18
|
+
app.get("/agent/debug/sessions/v1", async (raw) => {
|
|
19
|
+
const context = raw as DebugSessionRouteContext;
|
|
20
|
+
try {
|
|
21
|
+
return json(
|
|
22
|
+
await facade.list(context.guardContext, context.request.signal),
|
|
23
|
+
);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return facadeError(error, context.guardContext.requestId);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
app.get("/agent/debug/sessions/v1/:sessionId", async (raw) => {
|
|
29
|
+
const context = raw as DebugSessionRouteContext;
|
|
30
|
+
try {
|
|
31
|
+
return json(
|
|
32
|
+
await facade.get(
|
|
33
|
+
context.params.sessionId ?? "",
|
|
34
|
+
context.guardContext,
|
|
35
|
+
context.request.signal,
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return facadeError(error, context.guardContext.requestId);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return app;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function json(body: unknown, status = 200): Response {
|
|
46
|
+
return Response.json(body, {
|
|
47
|
+
status,
|
|
48
|
+
headers: { "cache-control": "no-store" },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function facadeError(error: unknown, requestId: string): Response {
|
|
53
|
+
if (error instanceof DebugSessionFacadeError) {
|
|
54
|
+
return json(
|
|
55
|
+
{
|
|
56
|
+
code: error.code,
|
|
57
|
+
message: error.message,
|
|
58
|
+
requestId,
|
|
59
|
+
retryable: error.status >= 500,
|
|
60
|
+
},
|
|
61
|
+
error.status,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return json(
|
|
65
|
+
{
|
|
66
|
+
code: "PROVIDER_UNAVAILABLE",
|
|
67
|
+
message: "调试会话 Provider 暂不可用",
|
|
68
|
+
requestId,
|
|
69
|
+
retryable: true,
|
|
70
|
+
},
|
|
71
|
+
503,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleDebugSessionSummaryV1,
|
|
3
|
+
ModuleDebugSessionV1,
|
|
4
|
+
} from "@southwind-ai/server-sdk";
|
|
5
|
+
|
|
6
|
+
const MAX_SESSIONS = 100;
|
|
7
|
+
const MAX_MESSAGES = 64;
|
|
8
|
+
const MAX_MESSAGE_CHARS = 16_000;
|
|
9
|
+
const MAX_TOTAL_MESSAGE_CHARS = 128_000;
|
|
10
|
+
|
|
11
|
+
export class DebugSessionProviderContractError extends Error {}
|
|
12
|
+
|
|
13
|
+
export function validateDebugSessionPage(value: {
|
|
14
|
+
sessions: readonly ModuleDebugSessionSummaryV1[];
|
|
15
|
+
truncated: boolean;
|
|
16
|
+
next?: string;
|
|
17
|
+
}): {
|
|
18
|
+
sessions: readonly ModuleDebugSessionSummaryV1[];
|
|
19
|
+
truncated: boolean;
|
|
20
|
+
next?: string;
|
|
21
|
+
} {
|
|
22
|
+
if (
|
|
23
|
+
!value ||
|
|
24
|
+
!Array.isArray(value.sessions) ||
|
|
25
|
+
typeof value.truncated !== "boolean" ||
|
|
26
|
+
value.sessions.length > MAX_SESSIONS ||
|
|
27
|
+
(value.truncated ? !validText(value.next, 2_000) : value.next !== undefined)
|
|
28
|
+
) {
|
|
29
|
+
invalidProvider();
|
|
30
|
+
}
|
|
31
|
+
const sourceIds = new Set<string>();
|
|
32
|
+
for (const session of value.sessions) {
|
|
33
|
+
validateSummary(session);
|
|
34
|
+
if (sourceIds.has(session.sourceId)) invalidProvider();
|
|
35
|
+
sourceIds.add(session.sourceId);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function validateDebugSessionDetail(
|
|
41
|
+
value: ModuleDebugSessionV1,
|
|
42
|
+
expectedSourceId: string,
|
|
43
|
+
): ModuleDebugSessionV1 {
|
|
44
|
+
validateSummary(value);
|
|
45
|
+
if (
|
|
46
|
+
value.sourceId !== expectedSourceId ||
|
|
47
|
+
!Array.isArray(value.messages) ||
|
|
48
|
+
value.messages.length > MAX_MESSAGES
|
|
49
|
+
) {
|
|
50
|
+
invalidProvider();
|
|
51
|
+
}
|
|
52
|
+
let previousSequence = -1;
|
|
53
|
+
let totalChars = 0;
|
|
54
|
+
for (const message of value.messages) {
|
|
55
|
+
if (
|
|
56
|
+
!message ||
|
|
57
|
+
!Number.isSafeInteger(message.sequence) ||
|
|
58
|
+
message.sequence <= previousSequence ||
|
|
59
|
+
(message.role !== "user" && message.role !== "assistant") ||
|
|
60
|
+
!validText(message.content, MAX_MESSAGE_CHARS, false) ||
|
|
61
|
+
!validDate(message.createdAt)
|
|
62
|
+
) {
|
|
63
|
+
invalidProvider();
|
|
64
|
+
}
|
|
65
|
+
totalChars += message.content.length;
|
|
66
|
+
previousSequence = message.sequence;
|
|
67
|
+
}
|
|
68
|
+
if (totalChars > MAX_TOTAL_MESSAGE_CHARS) invalidProvider();
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validateSummary(value: ModuleDebugSessionSummaryV1): void {
|
|
73
|
+
if (
|
|
74
|
+
!value ||
|
|
75
|
+
!validText(value.sourceId, 200) ||
|
|
76
|
+
!validText(value.title, 200) ||
|
|
77
|
+
(value.scopeLabel !== undefined && !validText(value.scopeLabel, 120)) ||
|
|
78
|
+
!validDate(value.updatedAt) ||
|
|
79
|
+
!validText(value.revision, 200)
|
|
80
|
+
) {
|
|
81
|
+
invalidProvider();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validText(value: unknown, max: number, trim = true): value is string {
|
|
86
|
+
return (
|
|
87
|
+
typeof value === "string" &&
|
|
88
|
+
value.length <= max &&
|
|
89
|
+
value.trim().length > 0 &&
|
|
90
|
+
(!trim || value === value.trim())
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validDate(value: unknown): value is string {
|
|
95
|
+
return (
|
|
96
|
+
typeof value === "string" &&
|
|
97
|
+
value === value.trim() &&
|
|
98
|
+
value.length <= 40 &&
|
|
99
|
+
/(?:Z|[+-]\d{2}:\d{2})$/.test(value) &&
|
|
100
|
+
Number.isFinite(Date.parse(value))
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function invalidProvider(): never {
|
|
105
|
+
throw new DebugSessionProviderContractError("调试会话 Provider 返回无效数据");
|
|
106
|
+
}
|
|
@@ -85,6 +85,7 @@ function values(binding: AgentRunBinding) {
|
|
|
85
85
|
actorId: binding.actorId,
|
|
86
86
|
idempotencyHash: binding.idempotencyHash,
|
|
87
87
|
tokenHash: binding.tokenHash,
|
|
88
|
+
inputDigestCommitment: binding.inputDigestCommitment,
|
|
88
89
|
scopeHash: binding.scopeHash,
|
|
89
90
|
scopeSnapshot: binding.scopeSnapshot,
|
|
90
91
|
authorizationSnapshot: binding.authorizationSnapshot,
|
|
@@ -103,6 +104,7 @@ function map(row: typeof agentRunBindings.$inferSelect): AgentRunBinding {
|
|
|
103
104
|
actorId: row.actorId,
|
|
104
105
|
idempotencyHash: row.idempotencyHash,
|
|
105
106
|
tokenHash: row.tokenHash,
|
|
107
|
+
inputDigestCommitment: row.inputDigestCommitment,
|
|
106
108
|
scopeHash: row.scopeHash,
|
|
107
109
|
scopeSnapshot: row.scopeSnapshot,
|
|
108
110
|
authorizationSnapshot: row.authorizationSnapshot,
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import type { AiProviderDataPolicy } from "@southwind-ai/modules";
|
|
3
3
|
import {
|
|
4
4
|
governProviderInput,
|
|
5
|
+
projectProviderInput,
|
|
5
6
|
ProviderInputGovernanceError,
|
|
6
7
|
} from "./provider-input-governance.js";
|
|
7
8
|
|
|
@@ -48,6 +49,23 @@ describe("Provider 输入字段治理", () => {
|
|
|
48
49
|
expect(JSON.stringify(governed)).not.toContain("case-secret");
|
|
49
50
|
});
|
|
50
51
|
|
|
52
|
+
test("承诺视图保留 allowlist 原值且不与脱敏占位符碰撞", () => {
|
|
53
|
+
expect(
|
|
54
|
+
projectProviderInput(
|
|
55
|
+
{
|
|
56
|
+
question: "联系 13800138000",
|
|
57
|
+
internalCaseId: "case-secret",
|
|
58
|
+
},
|
|
59
|
+
policy,
|
|
60
|
+
),
|
|
61
|
+
).toEqual({ question: "联系 13800138000" });
|
|
62
|
+
expect(
|
|
63
|
+
projectProviderInput({ question: "联系 [REDACTED]" }, policy),
|
|
64
|
+
).not.toEqual(
|
|
65
|
+
projectProviderInput({ question: "联系 13800138000" }, policy),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
51
69
|
test.each([
|
|
52
70
|
"password=do-not-send",
|
|
53
71
|
"Bearer abcdefghijklmnop",
|
|
@@ -25,6 +25,32 @@ const dlpPatterns = [
|
|
|
25
25
|
export function governProviderInput(
|
|
26
26
|
input: ModuleAiJsonValue,
|
|
27
27
|
policy: AiProviderDataPolicy,
|
|
28
|
+
): ModuleAiJsonValue {
|
|
29
|
+
return selectProviderInput(input, policy, (value, action) =>
|
|
30
|
+
governValue(value, action),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build the stable execution-relevant input view without applying lossy DLP.
|
|
36
|
+
*
|
|
37
|
+
* Run attestation commits this view so exact caller input remains
|
|
38
|
+
* distinguishable even when two values redact to the same provider payload.
|
|
39
|
+
*/
|
|
40
|
+
export function projectProviderInput(
|
|
41
|
+
input: ModuleAiJsonValue,
|
|
42
|
+
policy: AiProviderDataPolicy,
|
|
43
|
+
): ModuleAiJsonValue {
|
|
44
|
+
return selectProviderInput(input, policy, (value) => cloneValue(value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function selectProviderInput(
|
|
48
|
+
input: ModuleAiJsonValue,
|
|
49
|
+
policy: AiProviderDataPolicy,
|
|
50
|
+
transform: (
|
|
51
|
+
value: ModuleAiJsonValue,
|
|
52
|
+
action: AiProviderDlpAction,
|
|
53
|
+
) => ModuleAiJsonValue,
|
|
28
54
|
): ModuleAiJsonValue {
|
|
29
55
|
if (!isRecord(input)) {
|
|
30
56
|
throw new ProviderInputGovernanceError(
|
|
@@ -37,11 +63,21 @@ export function governProviderInput(
|
|
|
37
63
|
const segments = parseJsonPointer(rule.jsonPointer);
|
|
38
64
|
const result = readPath(input, segments);
|
|
39
65
|
if (!result.found) continue;
|
|
40
|
-
writePath(governed, segments,
|
|
66
|
+
writePath(governed, segments, transform(result.value, rule.dlpAction));
|
|
41
67
|
}
|
|
42
68
|
return governed;
|
|
43
69
|
}
|
|
44
70
|
|
|
71
|
+
function cloneValue(value: ModuleAiJsonValue): ModuleAiJsonValue {
|
|
72
|
+
if (Array.isArray(value)) return value.map(cloneValue);
|
|
73
|
+
if (isRecord(value)) {
|
|
74
|
+
return Object.fromEntries(
|
|
75
|
+
Object.entries(value).map(([key, item]) => [key, cloneValue(item)]),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
|
|
45
81
|
function governValue(
|
|
46
82
|
value: ModuleAiJsonValue,
|
|
47
83
|
action: AiProviderDlpAction,
|
|
@@ -105,6 +105,11 @@ describe("RemoteAgentRuntime", () => {
|
|
|
105
105
|
executionGrant: "grant_1",
|
|
106
106
|
release: runtimeRelease(),
|
|
107
107
|
traceId: "trace_request_1",
|
|
108
|
+
reporting: {
|
|
109
|
+
actorId: "user_1",
|
|
110
|
+
actorType: "user",
|
|
111
|
+
departmentId: "dept_1",
|
|
112
|
+
},
|
|
108
113
|
};
|
|
109
114
|
const runtime = new RemoteAgentRuntime({
|
|
110
115
|
baseUrl: server.url.toString(),
|
|
@@ -128,6 +133,7 @@ describe("RemoteAgentRuntime", () => {
|
|
|
128
133
|
toolNetworkPolicy: { mode: "forbidden" },
|
|
129
134
|
release: operation.release,
|
|
130
135
|
traceId: operation.traceId,
|
|
136
|
+
reporting: operation.reporting,
|
|
131
137
|
});
|
|
132
138
|
expect(
|
|
133
139
|
(received as { limits: Record<string, unknown> }).limits,
|
|
@@ -185,6 +191,7 @@ describe("RemoteAgentRuntime", () => {
|
|
|
185
191
|
executionGrant: "grant_1",
|
|
186
192
|
release: runtimeRelease(),
|
|
187
193
|
traceId: "trace_request_vision",
|
|
194
|
+
reporting: { actorId: "user_1", actorType: "user" },
|
|
188
195
|
};
|
|
189
196
|
|
|
190
197
|
await expect(
|
|
@@ -64,6 +64,7 @@ export class RemoteAgentRuntime implements AgentRuntimePort {
|
|
|
64
64
|
executionGrant: request.executionGrant,
|
|
65
65
|
release: request.release,
|
|
66
66
|
traceId: request.traceId,
|
|
67
|
+
reporting: request.reporting,
|
|
67
68
|
toolHostUrl: this.options.toolHostUrl,
|
|
68
69
|
};
|
|
69
70
|
const response = await this.request("/api/agent/runs", {
|
|
@@ -20,6 +20,7 @@ describe("Agent Run 持久授权绑定", () => {
|
|
|
20
20
|
operationKey: "business.answer",
|
|
21
21
|
releaseId: "release-1",
|
|
22
22
|
idempotencyKey: "request-1",
|
|
23
|
+
input: { question: "same" },
|
|
23
24
|
authorizationSnapshot: snapshot,
|
|
24
25
|
context: context("actor-1", "role-1"),
|
|
25
26
|
});
|
|
@@ -30,6 +31,7 @@ describe("Agent Run 持久授权绑定", () => {
|
|
|
30
31
|
operationKey: "business.answer",
|
|
31
32
|
releaseId: "release-1",
|
|
32
33
|
idempotencyKey: "request-1",
|
|
34
|
+
input: { question: "same" },
|
|
33
35
|
authorizationSnapshot: snapshot,
|
|
34
36
|
context: context("actor-1", "role-1"),
|
|
35
37
|
});
|
|
@@ -52,6 +54,7 @@ describe("Agent Run 持久授权绑定", () => {
|
|
|
52
54
|
operationKey: "business.answer",
|
|
53
55
|
releaseId: "release-1",
|
|
54
56
|
idempotencyKey: "request-fast-tool",
|
|
57
|
+
input: { question: "fast" },
|
|
55
58
|
authorizationSnapshot: snapshot,
|
|
56
59
|
context: context("actor-1", "role-1"),
|
|
57
60
|
});
|
|
@@ -75,6 +78,7 @@ describe("Agent Run 持久授权绑定", () => {
|
|
|
75
78
|
operationKey: "business.answer",
|
|
76
79
|
releaseId: "release-1",
|
|
77
80
|
idempotencyKey: "request-denied",
|
|
81
|
+
input: { question: "denied" },
|
|
78
82
|
authorizationSnapshot: snapshot,
|
|
79
83
|
context: context("actor-1", "role-1"),
|
|
80
84
|
});
|
|
@@ -102,6 +106,100 @@ describe("Agent Run 持久授权绑定", () => {
|
|
|
102
106
|
message: "Agent Run 不存在或不可访问",
|
|
103
107
|
});
|
|
104
108
|
});
|
|
109
|
+
|
|
110
|
+
test("输入对象键序不影响承诺,但相同幂等键不能绑定不同输入", async () => {
|
|
111
|
+
const store = new AgentRunAuthorizationStore(
|
|
112
|
+
new InMemoryAgentRunBindingRepository(),
|
|
113
|
+
secret,
|
|
114
|
+
);
|
|
115
|
+
const currentContext = context("actor-1", "role-1");
|
|
116
|
+
const first = await store.issue({
|
|
117
|
+
operationKey: "business.answer",
|
|
118
|
+
releaseId: "release-1",
|
|
119
|
+
idempotencyKey: "request-input",
|
|
120
|
+
input: { question: "same", nested: { b: 2, a: 1 } },
|
|
121
|
+
authorizationSnapshot: snapshot,
|
|
122
|
+
context: currentContext,
|
|
123
|
+
});
|
|
124
|
+
await store.bind(first, "run-input");
|
|
125
|
+
|
|
126
|
+
await expect(
|
|
127
|
+
store.issue({
|
|
128
|
+
operationKey: "business.answer",
|
|
129
|
+
releaseId: "release-1",
|
|
130
|
+
idempotencyKey: "request-input",
|
|
131
|
+
input: { nested: { a: 1, b: 2 }, question: "same" },
|
|
132
|
+
authorizationSnapshot: snapshot,
|
|
133
|
+
context: currentContext,
|
|
134
|
+
}),
|
|
135
|
+
).resolves.toMatchObject({ token: first.token });
|
|
136
|
+
await expect(
|
|
137
|
+
store.issue({
|
|
138
|
+
operationKey: "business.answer",
|
|
139
|
+
releaseId: "release-1",
|
|
140
|
+
idempotencyKey: "request-input",
|
|
141
|
+
input: { question: "different" },
|
|
142
|
+
authorizationSnapshot: snapshot,
|
|
143
|
+
context: currentContext,
|
|
144
|
+
}),
|
|
145
|
+
).rejects.toMatchObject({ message: "Agent Run 不存在或不可访问" });
|
|
146
|
+
await expect(
|
|
147
|
+
store.verifyRunInput(
|
|
148
|
+
"business.answer",
|
|
149
|
+
"run-input",
|
|
150
|
+
{ nested: { b: 2, a: 1 }, question: "same" },
|
|
151
|
+
currentContext,
|
|
152
|
+
),
|
|
153
|
+
).resolves.toBeDefined();
|
|
154
|
+
await expect(
|
|
155
|
+
store.verifyRunInput(
|
|
156
|
+
"business.answer",
|
|
157
|
+
"run-input",
|
|
158
|
+
{ question: "different" },
|
|
159
|
+
currentContext,
|
|
160
|
+
),
|
|
161
|
+
).rejects.toMatchObject({ message: "Agent Run 不存在或不可访问" });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("升级前绑定保留普通授权,但零值承诺不能通过输入验证", async () => {
|
|
165
|
+
const sourceStore = new AgentRunAuthorizationStore(
|
|
166
|
+
new InMemoryAgentRunBindingRepository(),
|
|
167
|
+
secret,
|
|
168
|
+
);
|
|
169
|
+
const currentContext = context("actor-1", "role-1");
|
|
170
|
+
const issued = await sourceStore.issue({
|
|
171
|
+
operationKey: "business.answer",
|
|
172
|
+
releaseId: "release-1",
|
|
173
|
+
idempotencyKey: "legacy-binding",
|
|
174
|
+
input: { question: "legacy" },
|
|
175
|
+
authorizationSnapshot: snapshot,
|
|
176
|
+
context: currentContext,
|
|
177
|
+
});
|
|
178
|
+
const repository = new InMemoryAgentRunBindingRepository();
|
|
179
|
+
await repository.createOrGet({
|
|
180
|
+
...issued.binding,
|
|
181
|
+
runId: "run-legacy",
|
|
182
|
+
boundAt: Date.now(),
|
|
183
|
+
inputDigestCommitment: "0".repeat(64),
|
|
184
|
+
});
|
|
185
|
+
const upgradedStore = new AgentRunAuthorizationStore(repository, secret);
|
|
186
|
+
|
|
187
|
+
await expect(
|
|
188
|
+
upgradedStore.authorizeBrowser(
|
|
189
|
+
"business.answer",
|
|
190
|
+
"run-legacy",
|
|
191
|
+
currentContext,
|
|
192
|
+
),
|
|
193
|
+
).resolves.toBeDefined();
|
|
194
|
+
await expect(
|
|
195
|
+
upgradedStore.verifyRunInput(
|
|
196
|
+
"business.answer",
|
|
197
|
+
"run-legacy",
|
|
198
|
+
{ question: "legacy" },
|
|
199
|
+
currentContext,
|
|
200
|
+
),
|
|
201
|
+
).rejects.toMatchObject({ message: "Agent Run 不存在或不可访问" });
|
|
202
|
+
});
|
|
105
203
|
});
|
|
106
204
|
|
|
107
205
|
function context(actorId: string, roleId: string) {
|
|
@@ -2,10 +2,15 @@ import { createHash, createHmac } from "node:crypto";
|
|
|
2
2
|
import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
|
|
3
3
|
import type { RequestGuardContext } from "../guards.js";
|
|
4
4
|
import type { AgentRunAuthorizationSnapshot } from "@southwind-ai/shared";
|
|
5
|
+
import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
|
|
5
6
|
import type {
|
|
6
7
|
AgentRunBinding,
|
|
7
8
|
AgentRunBindingRepository,
|
|
8
9
|
} from "./run-binding-repository.js";
|
|
10
|
+
import {
|
|
11
|
+
commitAgentRunInput,
|
|
12
|
+
sameInputCommitment,
|
|
13
|
+
} from "./run-input-commitment.js";
|
|
9
14
|
|
|
10
15
|
export interface AgentExecutionAuthorization {
|
|
11
16
|
readonly binding: AgentRunBinding;
|
|
@@ -28,6 +33,7 @@ export class AgentRunAuthorizationStore {
|
|
|
28
33
|
operationKey: string;
|
|
29
34
|
releaseId: string;
|
|
30
35
|
idempotencyKey: string;
|
|
36
|
+
input: ModuleAiJsonValue;
|
|
31
37
|
authorizationSnapshot: AgentRunAuthorizationSnapshot;
|
|
32
38
|
context: RequestGuardContext;
|
|
33
39
|
}): Promise<AgentExecutionAuthorization> {
|
|
@@ -41,12 +47,18 @@ export class AgentRunAuthorizationStore {
|
|
|
41
47
|
].join("\u0000");
|
|
42
48
|
const idempotencyHash = this.hmac(`idempotency\u0000${coordinate}`);
|
|
43
49
|
const token = `grant_${this.hmac(`token\u0000${coordinate}`)}`;
|
|
50
|
+
const tokenHash = sha256(token);
|
|
44
51
|
const candidate: AgentRunBinding = {
|
|
45
52
|
bindingId: `binding_${idempotencyHash.slice(0, 40)}`,
|
|
46
53
|
operationKey: input.operationKey,
|
|
47
54
|
actorId: input.context.actor.id,
|
|
48
55
|
idempotencyHash,
|
|
49
|
-
tokenHash
|
|
56
|
+
tokenHash,
|
|
57
|
+
inputDigestCommitment: commitAgentRunInput(
|
|
58
|
+
this.secret,
|
|
59
|
+
tokenHash,
|
|
60
|
+
input.input,
|
|
61
|
+
),
|
|
50
62
|
scopeHash: scope.hash,
|
|
51
63
|
scopeSnapshot: scope.snapshot,
|
|
52
64
|
authorizationSnapshot: structuredClone(input.authorizationSnapshot),
|
|
@@ -117,6 +129,24 @@ export class AgentRunAuthorizationStore {
|
|
|
117
129
|
return binding;
|
|
118
130
|
}
|
|
119
131
|
|
|
132
|
+
async verifyRunInput(
|
|
133
|
+
operationKey: string,
|
|
134
|
+
runId: string,
|
|
135
|
+
input: ModuleAiJsonValue,
|
|
136
|
+
context: RequestGuardContext,
|
|
137
|
+
) {
|
|
138
|
+
const binding = await this.authorizeBrowser(operationKey, runId, context);
|
|
139
|
+
const commitment = commitAgentRunInput(
|
|
140
|
+
this.secret,
|
|
141
|
+
binding.tokenHash,
|
|
142
|
+
input,
|
|
143
|
+
);
|
|
144
|
+
if (!sameInputCommitment(binding.inputDigestCommitment, commitment)) {
|
|
145
|
+
throw new AgentRunAuthorizationError();
|
|
146
|
+
}
|
|
147
|
+
return binding;
|
|
148
|
+
}
|
|
149
|
+
|
|
120
150
|
assertCurrentScope(
|
|
121
151
|
binding: AgentRunBinding,
|
|
122
152
|
context: RequestGuardContext,
|
|
@@ -151,6 +181,7 @@ function sameAuthorization(
|
|
|
151
181
|
existing.operationKey === candidate.operationKey &&
|
|
152
182
|
existing.actorId === candidate.actorId &&
|
|
153
183
|
existing.tokenHash === candidate.tokenHash &&
|
|
184
|
+
existing.inputDigestCommitment === candidate.inputDigestCommitment &&
|
|
154
185
|
existing.scopeHash === candidate.scopeHash &&
|
|
155
186
|
sameSnapshot(
|
|
156
187
|
existing.authorizationSnapshot,
|
|
@@ -10,6 +10,7 @@ export interface AgentRunBinding {
|
|
|
10
10
|
readonly actorId: string;
|
|
11
11
|
readonly idempotencyHash: string;
|
|
12
12
|
readonly tokenHash: string;
|
|
13
|
+
readonly inputDigestCommitment: string;
|
|
13
14
|
readonly scopeHash: string;
|
|
14
15
|
readonly scopeSnapshot: AuthorizationScopeSnapshot;
|
|
15
16
|
readonly authorizationSnapshot: AgentRunAuthorizationSnapshot;
|
|
@@ -38,6 +38,11 @@ export interface AgentRuntimeOperation {
|
|
|
38
38
|
executionGrant: string;
|
|
39
39
|
release: AgentRuntimeRelease;
|
|
40
40
|
traceId: string;
|
|
41
|
+
reporting: {
|
|
42
|
+
actorId: string;
|
|
43
|
+
actorType: "anonymous" | "user" | "service";
|
|
44
|
+
departmentId?: string;
|
|
45
|
+
};
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
export interface AgentRuntimeRelease {
|