create-windy 0.8.1 → 0.8.3

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 (95) hide show
  1. package/dist/cli.js +52 -34
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/apps/agent-server/src/southwind_agent_server/attempt_context.py +9 -1
  5. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +5 -2
  6. package/template/apps/agent-server/src/southwind_agent_server/provider_output_guidance.py +34 -0
  7. package/template/apps/agent-server/src/southwind_agent_server/structured_logging.py +16 -5
  8. package/template/apps/agent-server/tests/test_structured_logging.py +37 -0
  9. package/template/apps/agent-server/tests/test_tool_structured_output.py +347 -0
  10. package/template/apps/server/src/agent/admin-catalog-bootstrap.ts +25 -0
  11. package/template/apps/server/src/agent/agent-session-bootstrap.ts +7 -8
  12. package/template/apps/server/src/agent/agent-session-facade.ts +12 -0
  13. package/template/apps/server/src/agent/agent-session-validation.ts +43 -0
  14. package/template/apps/server/src/agent/operation-catalog.test.ts +202 -0
  15. package/template/apps/server/src/agent/operation-catalog.ts +135 -0
  16. package/template/apps/server/src/agent/run-browser-visibility.test.ts +59 -0
  17. package/template/apps/server/src/agent/run-browser-visibility.ts +11 -0
  18. package/template/apps/server/src/agent/run-operation-policy.ts +1 -0
  19. package/template/apps/server/src/agent/run-route-response.ts +35 -0
  20. package/template/apps/server/src/agent/run-routes.ts +46 -43
  21. package/template/apps/server/src/agent/runtime-bootstrap.ts +21 -20
  22. package/template/apps/server/src/agent/runtime-disabled-composition.ts +28 -0
  23. package/template/apps/server/src/agent/session-projection-facade.ts +8 -9
  24. package/template/apps/server/src/agent/session-projection-governance.ts +10 -0
  25. package/template/apps/server/src/agent/session-projection-view.ts +19 -16
  26. package/template/apps/server/src/agent/session-recording-reader.contract.test.ts +131 -0
  27. package/template/apps/server/src/agent/session-recording-reader.ts +62 -0
  28. package/template/apps/server/src/agent/tool-catalog-audit.ts +22 -0
  29. package/template/apps/server/src/agent/tool-catalog-availability.ts +21 -0
  30. package/template/apps/server/src/agent/tool-catalog-routes.test.ts +103 -0
  31. package/template/apps/server/src/agent/tool-catalog-routes.ts +62 -0
  32. package/template/apps/server/src/agent/tool-catalog.test.ts +122 -0
  33. package/template/apps/server/src/agent/tool-catalog.ts +44 -0
  34. package/template/apps/server/src/agent/tool-registry.ts +45 -14
  35. package/template/apps/server/src/agent/workbench-bootstrap.ts +39 -0
  36. package/template/apps/server/src/agent/workbench-event-projection.ts +115 -0
  37. package/template/apps/server/src/agent/workbench-facade.contract.test.ts +189 -0
  38. package/template/apps/server/src/agent/workbench-facade.ts +339 -0
  39. package/template/apps/server/src/agent/workbench-recording.contract.test.ts +328 -0
  40. package/template/apps/server/src/agent/workbench-routes.ts +97 -0
  41. package/template/apps/server/src/agent/workbench-run-integration.test.ts +231 -0
  42. package/template/apps/server/src/agent/workbench-run-reader.ts +187 -0
  43. package/template/apps/server/src/agent/workbench-safe-result.ts +61 -0
  44. package/template/apps/server/src/agent/workbench-sse.ts +50 -0
  45. package/template/apps/server/src/agent-workflow/query-service.ts +36 -0
  46. package/template/apps/server/src/agent-workflow/recording-projection.contract.test.ts +196 -0
  47. package/template/apps/server/src/agent-workflow/recording-projection.ts +86 -0
  48. package/template/apps/web/src/pages/system/AgentDebugPage.vue +14 -0
  49. package/template/apps/web/src/pages/system/AgentSessionPage.vue +29 -8
  50. package/template/apps/web/src/pages/system/AgentToolsPage.vue +76 -0
  51. package/template/apps/web/src/pages/system/components/agent-workbench/AgentWorkbench.vue +236 -0
  52. package/template/apps/web/src/pages/system/components/agent-workbench/DraftLeaveDialog.vue +34 -0
  53. package/template/apps/web/src/pages/system/components/agent-workbench/ReplayEventDetail.vue +175 -0
  54. package/template/apps/web/src/pages/system/components/agent-workbench/SessionReplayTimeline.vue +142 -0
  55. package/template/apps/web/src/pages/system/components/agent-workbench/SessionReplayTimeline.webtest.ts +242 -0
  56. package/template/apps/web/src/pages/system/components/agent-workbench/TestRunComposer.vue +97 -0
  57. package/template/apps/web/src/pages/system/components/agent-workbench/ToolCatalogPanel.vue +100 -0
  58. package/template/apps/web/src/pages/system/components/agent-workbench/ToolCatalogPanel.webtest.ts +55 -0
  59. package/template/apps/web/src/pages/system/components/agent-workbench/ToolRuleDetail.vue +81 -0
  60. package/template/apps/web/src/pages/system/components/agent-workbench/WorkbenchSessionList.vue +57 -0
  61. package/template/apps/web/src/pages/system/components/agent-workbench/catalog-presenter.ts +20 -0
  62. package/template/apps/web/src/pages/system/components/agent-workbench/replay-presenter.ts +94 -0
  63. package/template/apps/web/src/pages/system/composables/useAgentWorkbench.ts +298 -0
  64. package/template/apps/web/src/pages/system/composables/useAgentWorkbench.webtest.ts +164 -0
  65. package/template/apps/web/src/pages/system/composables/useSessionReplay.ts +48 -0
  66. package/template/apps/web/src/router/index.ts +4 -0
  67. package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
  68. package/template/apps/web/src/services/agent-admin-api.ts +155 -0
  69. package/template/apps/web/src/services/agent-admin-api.webtest.ts +120 -0
  70. package/template/apps/web/src/services/agent-workbench-event.ts +60 -0
  71. package/template/docs/architecture/agent-harness.md +4 -0
  72. package/template/docs/architecture/log-governance-implementation.md +20 -3
  73. package/template/docs/architecture/log-governance-plan.md +2 -2
  74. package/template/docs/platform/agent-runtime.md +18 -0
  75. package/template/packages/modules/src/ai-operation-composition.ts +9 -0
  76. package/template/packages/modules/src/ai-operation-visibility.test.ts +33 -0
  77. package/template/packages/modules/src/manifest.ts +2 -0
  78. package/template/packages/modules/src/system-admin-routes.ts +16 -0
  79. package/template/packages/modules/src/system-admin-ui-catalog.ts +7 -1
  80. package/template/packages/modules/src/system-agent-admin-bindings.ts +42 -0
  81. package/template/packages/modules/src/system-agent-admin-menus.ts +26 -0
  82. package/template/packages/modules/src/system-agent-admin-registration.test.ts +52 -0
  83. package/template/packages/modules/src/system-agent-audit-actions.ts +5 -0
  84. package/template/packages/modules/src/system-ai-api-bindings.ts +2 -0
  85. package/template/packages/modules/src/system-audit-actions.ts +25 -0
  86. package/template/packages/modules/src/system-features.ts +19 -0
  87. package/template/packages/modules/src/system-module-catalog.ts +14 -12
  88. package/template/packages/modules/src/system-module-ownership.ts +12 -0
  89. package/template/packages/modules/src/system-modules.ts +2 -0
  90. package/template/packages/modules/src/system-permissions.ts +5 -0
  91. package/template/packages/server-sdk/index.ts +2 -0
  92. package/template/packages/server-sdk/src/agent-session-provider.ts +19 -0
  93. package/template/packages/shared/index.ts +2 -0
  94. package/template/packages/shared/src/agent-admin-catalog.ts +73 -0
  95. package/template/packages/shared/src/agent-workbench.ts +100 -0
@@ -0,0 +1,135 @@
1
+ import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import type {
3
+ AgentAdminAvailabilityReason,
4
+ AgentAdminOperationCatalogItem,
5
+ } from "@southwind-ai/shared";
6
+ import { evaluateAccessGuards } from "../access-guards.js";
7
+ import { isServerScopeContext } from "../data-access/context.js";
8
+ import type { RequestGuardContext } from "../guards.js";
9
+ import type { InitializedModuleHosts } from "../module-host.js";
10
+ import type { createServerRuntime } from "../runtime.js";
11
+ import {
12
+ buildRegisteredOperations,
13
+ type RegisteredOperation,
14
+ } from "./run-operation-registry.js";
15
+ import { catalogGuardReason } from "./tool-catalog-availability.js";
16
+
17
+ export interface AgentAdminOperationCatalogInput {
18
+ readonly modules: readonly ModuleManifest[];
19
+ readonly hosts: InitializedModuleHosts;
20
+ readonly runtime: ReturnType<typeof createServerRuntime>;
21
+ readonly runtimeAvailable: () => boolean;
22
+ /** Optional current readiness projection; no Provider execution or grants. */
23
+ readonly operationReadiness?: (
24
+ operationKey: string,
25
+ context: RequestGuardContext,
26
+ ) => Promise<boolean>;
27
+ }
28
+
29
+ export class AgentAdminOperationCatalog {
30
+ readonly #operations: ReadonlyMap<string, RegisteredOperation>;
31
+ readonly #schemas: ReadonlyMap<
32
+ string,
33
+ NonNullable<ModuleManifest["jsonSchemas"]>[number]["schema"]
34
+ >;
35
+
36
+ constructor(private readonly input: AgentAdminOperationCatalogInput) {
37
+ this.#operations = buildRegisteredOperations(input.modules, input.hosts);
38
+ this.#schemas = new Map(
39
+ input.modules.flatMap(({ jsonSchemas }) =>
40
+ (jsonSchemas ?? []).map(({ key, schema }) => [key, schema] as const),
41
+ ),
42
+ );
43
+ }
44
+
45
+ async list(
46
+ context: RequestGuardContext,
47
+ ): Promise<readonly AgentAdminOperationCatalogItem[]> {
48
+ return Promise.all(
49
+ [...this.#operations.values()].map(async (operation) => {
50
+ const definition = operation.definition;
51
+ const module = this.input.modules.find(({ aiOperations }) =>
52
+ aiOperations?.some(({ key }) => key === definition.key),
53
+ )!;
54
+ const inputSchema = this.#schemas.get(definition.inputSchemaKey);
55
+ const outputSchema = this.#schemas.get(definition.outputSchemaKey);
56
+ const availabilityReason = await this.availability(operation, context);
57
+ return {
58
+ key: definition.key,
59
+ module: module.name,
60
+ moduleLabel: module.label,
61
+ kind: definition.kind,
62
+ visibility: definition.visibility ?? "public",
63
+ execution: definition.execution,
64
+ featureKey: definition.featureKey,
65
+ permissionKey: definition.permissionKey,
66
+ auditAction: definition.auditAction,
67
+ dataClassification: definition.dataClassification,
68
+ requiredCapabilities: [...definition.requiredCapabilities],
69
+ inputModalities: [...(definition.inputModalities ?? ["text"])],
70
+ inputSchemaKey: definition.inputSchemaKey,
71
+ outputSchemaKey: definition.outputSchemaKey,
72
+ ...(inputSchema ? { inputSchema: structuredClone(inputSchema) } : {}),
73
+ ...(outputSchema
74
+ ? { outputSchema: structuredClone(outputSchema) }
75
+ : {}),
76
+ allowedToolKeys: [...definition.allowedToolKeys],
77
+ limits: { ...definition.limits },
78
+ launchAuthority:
79
+ definition.clientExecution?.launchAuthority ?? "direct",
80
+ selectable: availabilityReason === "allowed",
81
+ availabilityReason,
82
+ };
83
+ }),
84
+ );
85
+ }
86
+
87
+ private async availability(
88
+ operation: RegisteredOperation,
89
+ context: RequestGuardContext,
90
+ ): Promise<AgentAdminAvailabilityReason> {
91
+ const { definition } = operation;
92
+ if (!operation.feature) return "governance-unavailable";
93
+ const access = await evaluateAccessGuards(
94
+ this.input.runtime,
95
+ context,
96
+ {
97
+ feature: operation.feature,
98
+ permission: { permissionKey: definition.permissionKey },
99
+ },
100
+ { record: false },
101
+ );
102
+ if (!access.decision.allowed)
103
+ return catalogGuardReason(access.decision.reason);
104
+ if (definition.visibility === "internal") return "internal-operation";
105
+ if (definition.kind !== "agent") return "unsupported-operation";
106
+ if (definition.clientExecution?.launchAuthority === "workflow-artifact")
107
+ return "workflow-only";
108
+ if (!operation.registration?.prepare || !operation.moduleName)
109
+ return "handler-unavailable";
110
+ if (!isServerScopeContext(context.actor.scopeContext))
111
+ return "scope-unavailable";
112
+ if (
113
+ !this.#schemas.has(definition.inputSchemaKey) ||
114
+ !this.#schemas.has(definition.outputSchemaKey)
115
+ )
116
+ return "governance-unavailable";
117
+ if (
118
+ definition.providerEgress?.mode === "configured-endpoint" &&
119
+ !definition.providerDataPolicy
120
+ )
121
+ return "governance-unavailable";
122
+ if (definition.isolatedExecution || !this.input.runtimeAvailable())
123
+ return "runtime-unavailable";
124
+ if (definition.readiness) {
125
+ if (!this.input.operationReadiness) return "runtime-unavailable";
126
+ try {
127
+ if (!(await this.input.operationReadiness(definition.key, context)))
128
+ return "runtime-unavailable";
129
+ } catch {
130
+ return "runtime-unavailable";
131
+ }
132
+ }
133
+ return "allowed";
134
+ }
135
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { AgentRunFacade } from "./run-facade.js";
3
+ import { assertBrowserVisibleOperation } from "./run-browser-visibility.js";
4
+ import {
5
+ createRunFacadeFixture,
6
+ runFacadeManifest,
7
+ } from "./run-facade.test-support.js";
8
+ import { createTestRunAuthorizationStore } from "./run-authorization.test-support.js";
9
+ import { AgentAdminOperationCatalog } from "./operation-catalog.js";
10
+
11
+ describe("Internal operations remain private to trusted orchestration", () => {
12
+ test("Public/default operations remain browser-visible", async () => {
13
+ const { facade } = await createRunFacadeFixture();
14
+ expect(facade.operationPolicy("business.answer")?.visibility).toBe(
15
+ "public",
16
+ );
17
+ expect(() =>
18
+ assertBrowserVisibleOperation(facade, "business.answer"),
19
+ ).not.toThrow();
20
+ });
21
+
22
+ test("Internal declaration blocks browser access and Admin selection without blocking governed server orchestration", async () => {
23
+ const { hosts, runtime, remote, context } = await createRunFacadeFixture();
24
+ const module = runFacadeManifest();
25
+ module.aiOperations![0]!.visibility = "internal";
26
+ const facade = new AgentRunFacade(
27
+ [module],
28
+ hosts,
29
+ runtime,
30
+ remote,
31
+ createTestRunAuthorizationStore(),
32
+ );
33
+ expect(() =>
34
+ assertBrowserVisibleOperation(facade, "business.answer"),
35
+ ).toThrow("Agent Run 不存在或不可访问");
36
+ const catalog = new AgentAdminOperationCatalog({
37
+ modules: [module],
38
+ hosts,
39
+ runtime,
40
+ runtimeAvailable: () => true,
41
+ });
42
+ expect((await catalog.list(context))[0]).toMatchObject({
43
+ visibility: "internal",
44
+ selectable: false,
45
+ availabilityReason: "internal-operation",
46
+ });
47
+ await facade.start(
48
+ "business.answer",
49
+ { question: "可信服务端编排" },
50
+ "server-orchestration",
51
+ context,
52
+ );
53
+ expect(remote.started).toHaveLength(1);
54
+ context.actor.permissionKeys = [];
55
+ await expect(
56
+ facade.start("business.answer", { question: "无权" }, "denied", context),
57
+ ).rejects.toMatchObject({ code: "FORBIDDEN" });
58
+ });
59
+ });
@@ -0,0 +1,11 @@
1
+ import type { AgentRunFacade } from "./run-facade.js";
2
+ import { inaccessibleRun } from "./run-errors.js";
3
+
4
+ export function assertBrowserVisibleOperation(
5
+ facade: AgentRunFacade,
6
+ operationKey: string,
7
+ ) {
8
+ if (facade.operationPolicy(operationKey)?.visibility === "internal") {
9
+ throw inaccessibleRun();
10
+ }
11
+ }
@@ -12,6 +12,7 @@ export function publicAgentOperationPolicy(
12
12
  allowedToolKeys: [...operation.definition.allowedToolKeys],
13
13
  featureKey: operation.definition.featureKey,
14
14
  permissionKey: operation.definition.permissionKey,
15
+ visibility: operation.definition.visibility ?? "public",
15
16
  deploymentKey:
16
17
  provider.mode === "configured-endpoint"
17
18
  ? provider.deploymentKey
@@ -0,0 +1,35 @@
1
+ import { AgentRunFacadeError } from "./run-errors.js";
2
+
3
+ export function facadeError(error: unknown): Response {
4
+ if (error instanceof AgentRunFacadeError) {
5
+ return errorResponse(error.code, error.message, error.status);
6
+ }
7
+ return errorResponse(
8
+ "RUNTIME_UNAVAILABLE",
9
+ "Agent Runtime 暂不可用",
10
+ 503,
11
+ true,
12
+ );
13
+ }
14
+
15
+ export function errorResponse(
16
+ code: string,
17
+ message: string,
18
+ status: number,
19
+ retryable = false,
20
+ ): Response {
21
+ return Response.json(
22
+ { code, message, requestId: crypto.randomUUID(), retryable },
23
+ { status },
24
+ );
25
+ }
26
+
27
+ export function required(value: string | undefined): string {
28
+ if (!value)
29
+ throw new AgentRunFacadeError("INVALID_INPUT", "Run ID 无效", 400);
30
+ return value;
31
+ }
32
+
33
+ export function isRecord(value: unknown): value is Record<string, unknown> {
34
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
35
+ }
@@ -1,3 +1,9 @@
1
+ import {
2
+ facadeError,
3
+ errorResponse,
4
+ required,
5
+ isRecord,
6
+ } from "./run-route-response.js";
1
7
  import { timingSafeEqual } from "node:crypto";
2
8
  import type { RequestGuardContext } from "../guards.js";
3
9
  import type { RouteApp } from "../system/routes.js";
@@ -7,6 +13,8 @@ import type { AgentRuntimePort } from "./run-contracts.js";
7
13
  import type { AgentRunSessionLinkPort } from "./session-run-link.js";
8
14
  import type { SessionRunResultMemoryPort } from "./session-result-memory.js";
9
15
  import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
16
+ import { projectPublicRunEvents } from "./workbench-sse.js";
17
+ import { assertBrowserVisibleOperation } from "./run-browser-visibility.js";
10
18
  import {
11
19
  AgentToolHostError,
12
20
  attachExecutionGrant,
@@ -46,6 +54,7 @@ export function registerAgentRunRoutes(
46
54
  const context = raw as RouteContext;
47
55
  try {
48
56
  const request = parseStartRequest(context.body);
57
+ assertBrowserVisibleOperation(facade, request.operationKey);
49
58
  const idempotencyKey = context.request.headers.get("idempotency-key");
50
59
  if (!idempotencyKey?.trim()) {
51
60
  return errorResponse("INVALID_INPUT", "幂等键不能为空", 400);
@@ -70,12 +79,15 @@ export function registerAgentRunRoutes(
70
79
  try {
71
80
  const runId = required(context.params.runId);
72
81
  const operationKey = parseOperationKey(context.query.operationKey);
82
+ assertBrowserVisibleOperation(facade, operationKey);
73
83
  await facade.authorizeRun(operationKey, runId, context.guardContext);
74
84
  const cursor = parseCursor(
75
85
  context.query.after,
76
86
  context.request.headers.get("last-event-id"),
77
87
  );
78
- return await remote.events(runId, cursor, context.request.signal);
88
+ return projectPublicRunEvents(
89
+ await remote.events(runId, cursor, context.request.signal),
90
+ );
79
91
  } catch (error) {
80
92
  return facadeError(error);
81
93
  }
@@ -86,6 +98,7 @@ export function registerAgentRunRoutes(
86
98
  try {
87
99
  const runId = required(context.params.runId);
88
100
  const operationKey = parseOperationKey(context.query.operationKey);
101
+ assertBrowserVisibleOperation(facade, operationKey);
89
102
  const sessionId = context.query.sessionId
90
103
  ? parseSessionId(context.query.sessionId)
91
104
  : undefined;
@@ -105,8 +118,14 @@ export function registerAgentRunRoutes(
105
118
  status.status === "cancelled"
106
119
  ) {
107
120
  if (status.status === "completed") {
108
- await projectResult(resultMemory, boundSessionId, runId,
109
- status.lastSequence, response, context);
121
+ await projectResult(
122
+ resultMemory,
123
+ boundSessionId,
124
+ runId,
125
+ status.lastSequence,
126
+ response,
127
+ context,
128
+ );
110
129
  }
111
130
  await completeSessionRun(
112
131
  sessionLinks,
@@ -128,6 +147,7 @@ export function registerAgentRunRoutes(
128
147
  try {
129
148
  const runId = required(context.params.runId);
130
149
  const operationKey = parseOperationKey(context.query.operationKey);
150
+ assertBrowserVisibleOperation(facade, operationKey);
131
151
  const sessionId = context.query.sessionId
132
152
  ? parseSessionId(context.query.sessionId)
133
153
  : undefined;
@@ -141,7 +161,11 @@ export function registerAgentRunRoutes(
141
161
  isRecord(context.body) && typeof context.body.reason === "string"
142
162
  ? context.body.reason.slice(0, 200)
143
163
  : undefined;
144
- const response = await remote.cancel(runId, reason, context.request.signal);
164
+ const response = await remote.cancel(
165
+ runId,
166
+ reason,
167
+ context.request.signal,
168
+ );
145
169
  if (authorized.sessionId && response.ok) {
146
170
  const status = await remote.status(runId, context.request.signal);
147
171
  if (!["completed", "failed", "cancelled"].includes(status.status)) {
@@ -149,8 +173,14 @@ export function registerAgentRunRoutes(
149
173
  }
150
174
  if (status.status === "completed") {
151
175
  const result = await remote.result(runId, context.request.signal);
152
- await projectResult(resultMemory, authorized.sessionId, runId,
153
- status.lastSequence, result, context);
176
+ await projectResult(
177
+ resultMemory,
178
+ authorized.sessionId,
179
+ runId,
180
+ status.lastSequence,
181
+ result,
182
+ context,
183
+ );
154
184
  }
155
185
  await completeSessionRun(
156
186
  sessionLinks,
@@ -171,6 +201,7 @@ export function registerAgentRunRoutes(
171
201
  try {
172
202
  const runId = required(context.params.runId);
173
203
  const request = parseAttachRequest(context.body);
204
+ assertBrowserVisibleOperation(facade, request.operationKey);
174
205
  const authorized = await facade.authorizeRunMutation(
175
206
  request.operationKey,
176
207
  runId,
@@ -188,8 +219,14 @@ export function registerAgentRunRoutes(
188
219
  if (authorized.sessionId && attached.terminalEvent) {
189
220
  if (attached.status === "completed") {
190
221
  const response = await remote.result(runId, context.request.signal);
191
- await projectResult(resultMemory, authorized.sessionId, runId,
192
- attached.terminalEvent.sequence, response, context);
222
+ await projectResult(
223
+ resultMemory,
224
+ authorized.sessionId,
225
+ runId,
226
+ attached.terminalEvent.sequence,
227
+ response,
228
+ context,
229
+ );
193
230
  }
194
231
  await completeSessionRun(
195
232
  sessionLinks,
@@ -267,7 +304,7 @@ async function projectResult(
267
304
  sessionId,
268
305
  runId,
269
306
  terminalSequence,
270
- result: await response.clone().json() as ModuleAiJsonValue,
307
+ result: (await response.clone().json()) as ModuleAiJsonValue,
271
308
  context: context.guardContext,
272
309
  signal: context.request.signal,
273
310
  });
@@ -302,37 +339,3 @@ export function validInternalToken(
302
339
  const right = Buffer.from(expected);
303
340
  return left.length === right.length && timingSafeEqual(left, right);
304
341
  }
305
-
306
- function facadeError(error: unknown): Response {
307
- if (error instanceof AgentRunFacadeError) {
308
- return errorResponse(error.code, error.message, error.status);
309
- }
310
- return errorResponse(
311
- "RUNTIME_UNAVAILABLE",
312
- "Agent Runtime 暂不可用",
313
- 503,
314
- true,
315
- );
316
- }
317
-
318
- function errorResponse(
319
- code: string,
320
- message: string,
321
- status: number,
322
- retryable = false,
323
- ): Response {
324
- return Response.json(
325
- { code, message, requestId: crypto.randomUUID(), retryable },
326
- { status },
327
- );
328
- }
329
-
330
- function required(value: string | undefined): string {
331
- if (!value)
332
- throw new AgentRunFacadeError("INVALID_INPUT", "Run ID 无效", 400);
333
- return value;
334
- }
335
-
336
- function isRecord(value: unknown): value is Record<string, unknown> {
337
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
338
- }
@@ -1,4 +1,6 @@
1
1
  import { resolveCurrentSystemActor } from "../system/actor.js";
2
+ import type { AgentAdminReadinessRef } from "./admin-catalog-bootstrap.js";
3
+ import { registerAgentAdminCatalogRuntime } from "./admin-catalog-bootstrap.js";
2
4
  import { AiArtifactCodec } from "../ai-artifact/codec.js";
3
5
  import { registerAiArtifactRoutes } from "../ai-artifact/routes.js";
4
6
  import { AiArtifactRuntime } from "../ai-artifact/runtime.js";
@@ -41,7 +43,7 @@ import { InMemoryAgentRunBindingRepository } from "./run-binding-repository.js";
41
43
  import { AgentBackgroundOperationExecutor } from "./background-operation.js";
42
44
  import { createModuleAiPorts } from "./module-ai-port-factory.js";
43
45
  import { createManifestAuthorizedRouteApp } from "../module-host/static-route-authorization.js";
44
- import { registerUnavailableAiRoutes } from "./unavailable-routes.js";
46
+ import { disableAgentRuntime } from "./runtime-disabled-composition.js";
45
47
  import { createClientAgentSessionRuntime } from "./client-session/bootstrap.js";
46
48
  import { attachClientAgentWorkflowArtifactRuntime } from "./client-workflow-artifact/runtime-composition.js";
47
49
  import { bindClientAgentHostRevocations } from "./client-session/host-revocation-composition.js";
@@ -52,6 +54,7 @@ import { registerScreenshotIntakeRoutes } from "./screenshot-intake-routes.js";
52
54
  import { createScreenshotIntakeRuntimeV2 } from "./screenshot-intake-bootstrap.js";
53
55
  import { requiredEnvironment } from "./required-environment.js";
54
56
  import { composeAgentLongSessionRuntime } from "./session-runtime-composition.js";
57
+ import { registerAgentWorkbenchRuntime } from "./workbench-bootstrap.js";
55
58
  import type { AgentPlatformRuntimeInput } from "./runtime-bootstrap-input.js";
56
59
  import { composeAgentToolRuntime } from "./runtime-tool-composition.js";
57
60
  import { registerProductionAgentWorkflowRuntime } from "./workflow-runtime-bootstrap.js";
@@ -62,33 +65,23 @@ import {
62
65
  export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
63
66
  const resolveAgentActor = async (actorId: string) =>
64
67
  resolveCurrentSystemActor(input.systemRepositories, actorId);
65
- const { features, toolHandlers, toolRegistry, routeAuthorizer } =
66
- composeAgentToolRuntime(input);
68
+ const tools = composeAgentToolRuntime(input);
69
+ const { features, toolHandlers, toolRegistry, routeAuthorizer } = tools;
70
+ const catalogReadiness: AgentAdminReadinessRef = {};
71
+ registerAgentAdminCatalogRuntime(input, tools, catalogReadiness);
67
72
  const aiRouteApp = createManifestAuthorizedRouteApp(
68
73
  input.app,
69
74
  routeAuthorizer,
70
75
  );
71
76
  if (!input.platformConfig.enableAI) {
72
- registerProductionAgentWorkflowRuntime(input, aiRouteApp, toolRegistry);
73
- registerUnavailableAiRoutes(
74
- createManifestAuthorizedRouteApp(input.app, routeAuthorizer),
75
- );
76
- return {
77
- toolHandlers,
78
- aiArtifacts: undefined,
79
- aiOperations: undefined,
80
- aiReadiness: undefined,
81
- clientAgentSessions: undefined,
82
- agentWorkflowProjects: undefined,
83
- sessionMemory: undefined,
84
- sourceInvalidation: undefined,
85
- invalidateAuthorization: undefined,
86
- disposeAgentRuntime: undefined,
87
- };
77
+ return disableAgentRuntime(input, aiRouteApp, tools);
88
78
  }
89
79
  const sessionSecret = requiredEnvironment("AI_ARTIFACT_ENCRYPTION_SECRET");
90
80
  const internalToken = requiredEnvironment("AGENT_SERVER_INTERNAL_TOKEN");
91
- registerAgentSessions({ ...input, secret: internalToken });
81
+ const businessSessions = registerAgentSessions({
82
+ ...input,
83
+ secret: internalToken,
84
+ });
92
85
  const aiArtifacts = new AiArtifactRuntime(
93
86
  new AiArtifactCodec(sessionSecret),
94
87
  input.hosts,
@@ -203,6 +196,7 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
203
196
  trustStore: readinessTrustStore,
204
197
  runtime: input.runtime,
205
198
  });
199
+ catalogReadiness.current = aiReadiness;
206
200
  const outputRuntime = createDocumentOutputRuntime({
207
201
  modules: input.modules,
208
202
  readiness: aiReadiness,
@@ -286,6 +280,13 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
286
280
  if (agentWorkflowRuntime?.artifacts) {
287
281
  clientWorkflowArtifacts.bindTaskArtifacts?.(agentWorkflowRuntime.artifacts);
288
282
  }
283
+ registerAgentWorkbenchRuntime(input, aiRouteApp, {
284
+ agentFacade,
285
+ remote,
286
+ business: businessSessions,
287
+ sessions: longSessions?.facade,
288
+ workflows: agentWorkflowRuntime?.query,
289
+ });
289
290
  registerScreenshotIntakeRoutes(aiRouteApp, screenshotIntake);
290
291
  registerAgentRunRoutes(
291
292
  aiRouteApp,
@@ -0,0 +1,28 @@
1
+ import type { RouteApp } from "../system/routes.js";
2
+ import type { AgentPlatformRuntimeInput } from "./runtime-bootstrap-input.js";
3
+ import type { composeAgentToolRuntime } from "./runtime-tool-composition.js";
4
+ import { registerAgentWorkbenchRuntime } from "./workbench-bootstrap.js";
5
+ import { registerProductionAgentWorkflowRuntime } from "./workflow-runtime-bootstrap.js";
6
+ import { registerUnavailableAiRoutes } from "./unavailable-routes.js";
7
+
8
+ export function disableAgentRuntime(
9
+ input: AgentPlatformRuntimeInput,
10
+ app: RouteApp,
11
+ tools: ReturnType<typeof composeAgentToolRuntime>,
12
+ ) {
13
+ registerAgentWorkbenchRuntime(input, app);
14
+ registerProductionAgentWorkflowRuntime(input, app, tools.toolRegistry);
15
+ registerUnavailableAiRoutes(app);
16
+ return {
17
+ toolHandlers: tools.toolHandlers,
18
+ aiArtifacts: undefined,
19
+ aiOperations: undefined,
20
+ aiReadiness: undefined,
21
+ clientAgentSessions: undefined,
22
+ agentWorkflowProjects: undefined,
23
+ sessionMemory: undefined,
24
+ sourceInvalidation: undefined,
25
+ invalidateAuthorization: undefined,
26
+ disposeAgentRuntime: undefined,
27
+ };
28
+ }
@@ -1,4 +1,5 @@
1
- import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
1
+ import { projectSessionRecording } from "./session-recording-reader.js";
2
+ import { sessionProjectionGovernance as governance } from "./session-projection-governance.js";
2
3
  import type { RequestGuardContext } from "../guards.js";
3
4
  import type { MemoryObjectAuthorizationPort } from "./context-memory/contracts.js";
4
5
  import {
@@ -89,6 +90,12 @@ export class AgentSessionProjectionFacade {
89
90
  };
90
91
  }
91
92
 
93
+ async recording(sessionId: string, context: RequestGuardContext) {
94
+ return projectSessionRecording(
95
+ await this.replay(sessionId, undefined, context),
96
+ );
97
+ }
98
+
92
99
  async contextVersions(sessionId: string, context: RequestGuardContext) {
93
100
  const session = await this.requireSession(sessionId, context);
94
101
  const rows = await this.contexts.listForSession(session.sessionId);
@@ -339,11 +346,3 @@ export class AgentSessionProjectionFacade {
339
346
  return session;
340
347
  }
341
348
  }
342
-
343
- function governance(context: RequestGuardContext) {
344
- return {
345
- actorId: context.actor.id,
346
- scopeDigest:
347
- `sha256:${captureAuthorizationScope(context.actor.scopeContext).hash}` as const,
348
- };
349
- }
@@ -0,0 +1,10 @@
1
+ import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
2
+ import type { RequestGuardContext } from "../guards.js";
3
+
4
+ export function sessionProjectionGovernance(context: RequestGuardContext) {
5
+ return {
6
+ actorId: context.actor.id,
7
+ scopeDigest:
8
+ `sha256:${captureAuthorizationScope(context.actor.scopeContext).hash}` as const,
9
+ };
10
+ }
@@ -27,9 +27,7 @@ export function projectAgentSession(session: SessionRecord) {
27
27
  };
28
28
  }
29
29
 
30
- export function projectAgentSessionTimelineEvent(
31
- event: SessionEventEnvelope,
32
- ) {
30
+ export function projectAgentSessionTimelineEvent(event: SessionEventEnvelope) {
33
31
  const data = event.data;
34
32
  return {
35
33
  sequence: event.sequence,
@@ -39,6 +37,9 @@ export function projectAgentSessionTimelineEvent(
39
37
  ...("runId" in data && data.runId ? { runId: data.runId } : {}),
40
38
  ...("turnId" in data ? { turnId: data.turnId } : {}),
41
39
  ...("stepId" in data ? { stepId: data.stepId } : {}),
40
+ ...("toolCallId" in data ? { toolCallId: data.toolCallId } : {}),
41
+ ...("toolKey" in data ? { toolKey: data.toolKey } : {}),
42
+ ...("outcome" in data ? { outcome: data.outcome } : {}),
42
43
  };
43
44
  }
44
45
 
@@ -121,17 +122,19 @@ export function projectableMemory(
121
122
  }
122
123
 
123
124
  function timelineSummary(type: SessionEventEnvelope["data"]["type"]) {
124
- return ({
125
- "session.created": "Session 已创建",
126
- "session.archived": "Session 已归档",
127
- "turn.started": "Turn 已开始",
128
- "turn.completed": "Turn 已结束",
129
- "run.linked": "Run 已关联",
130
- "step.started": "Step 已开始",
131
- "step.completed": "Step 已结束",
132
- "tool.execution-recorded": "Tool 执行事实已记录",
133
- "checkpoint.created": "Checkpoint 已创建",
134
- "fork.created": "Fork 已创建",
135
- "write-tool.reexecuted": "写 Tool 已经显式授权重新执行",
136
- } satisfies Record<SessionEventEnvelope["data"]["type"], string>)[type];
125
+ return (
126
+ {
127
+ "session.created": "Session 已创建",
128
+ "session.archived": "Session 已归档",
129
+ "turn.started": "Turn 已开始",
130
+ "turn.completed": "Turn 已结束",
131
+ "run.linked": "Run 已关联",
132
+ "step.started": "Step 已开始",
133
+ "step.completed": "Step 已结束",
134
+ "tool.execution-recorded": "Tool 执行事实已记录",
135
+ "checkpoint.created": "Checkpoint 已创建",
136
+ "fork.created": "Fork 已创建",
137
+ "write-tool.reexecuted": "写 Tool 已经显式授权重新执行",
138
+ } satisfies Record<SessionEventEnvelope["data"]["type"], string>
139
+ )[type];
137
140
  }