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.
- package/dist/cli.js +52 -34
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/agent-server/src/southwind_agent_server/attempt_context.py +9 -1
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +5 -2
- package/template/apps/agent-server/src/southwind_agent_server/provider_output_guidance.py +34 -0
- package/template/apps/agent-server/src/southwind_agent_server/structured_logging.py +16 -5
- package/template/apps/agent-server/tests/test_structured_logging.py +37 -0
- package/template/apps/agent-server/tests/test_tool_structured_output.py +347 -0
- package/template/apps/server/src/agent/admin-catalog-bootstrap.ts +25 -0
- package/template/apps/server/src/agent/agent-session-bootstrap.ts +7 -8
- package/template/apps/server/src/agent/agent-session-facade.ts +12 -0
- package/template/apps/server/src/agent/agent-session-validation.ts +43 -0
- package/template/apps/server/src/agent/operation-catalog.test.ts +202 -0
- package/template/apps/server/src/agent/operation-catalog.ts +135 -0
- package/template/apps/server/src/agent/run-browser-visibility.test.ts +59 -0
- package/template/apps/server/src/agent/run-browser-visibility.ts +11 -0
- package/template/apps/server/src/agent/run-operation-policy.ts +1 -0
- package/template/apps/server/src/agent/run-route-response.ts +35 -0
- package/template/apps/server/src/agent/run-routes.ts +46 -43
- package/template/apps/server/src/agent/runtime-bootstrap.ts +21 -20
- package/template/apps/server/src/agent/runtime-disabled-composition.ts +28 -0
- package/template/apps/server/src/agent/session-projection-facade.ts +8 -9
- package/template/apps/server/src/agent/session-projection-governance.ts +10 -0
- package/template/apps/server/src/agent/session-projection-view.ts +19 -16
- package/template/apps/server/src/agent/session-recording-reader.contract.test.ts +131 -0
- package/template/apps/server/src/agent/session-recording-reader.ts +62 -0
- package/template/apps/server/src/agent/tool-catalog-audit.ts +22 -0
- package/template/apps/server/src/agent/tool-catalog-availability.ts +21 -0
- package/template/apps/server/src/agent/tool-catalog-routes.test.ts +103 -0
- package/template/apps/server/src/agent/tool-catalog-routes.ts +62 -0
- package/template/apps/server/src/agent/tool-catalog.test.ts +122 -0
- package/template/apps/server/src/agent/tool-catalog.ts +44 -0
- package/template/apps/server/src/agent/tool-registry.ts +45 -14
- package/template/apps/server/src/agent/workbench-bootstrap.ts +39 -0
- package/template/apps/server/src/agent/workbench-event-projection.ts +115 -0
- package/template/apps/server/src/agent/workbench-facade.contract.test.ts +189 -0
- package/template/apps/server/src/agent/workbench-facade.ts +339 -0
- package/template/apps/server/src/agent/workbench-recording.contract.test.ts +328 -0
- package/template/apps/server/src/agent/workbench-routes.ts +97 -0
- package/template/apps/server/src/agent/workbench-run-integration.test.ts +231 -0
- package/template/apps/server/src/agent/workbench-run-reader.ts +187 -0
- package/template/apps/server/src/agent/workbench-safe-result.ts +61 -0
- package/template/apps/server/src/agent/workbench-sse.ts +50 -0
- package/template/apps/server/src/agent-workflow/query-service.ts +36 -0
- package/template/apps/server/src/agent-workflow/recording-projection.contract.test.ts +196 -0
- package/template/apps/server/src/agent-workflow/recording-projection.ts +86 -0
- package/template/apps/web/src/pages/system/AgentDebugPage.vue +14 -0
- package/template/apps/web/src/pages/system/AgentSessionPage.vue +29 -8
- package/template/apps/web/src/pages/system/AgentToolsPage.vue +76 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/AgentWorkbench.vue +236 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/DraftLeaveDialog.vue +34 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/ReplayEventDetail.vue +175 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/SessionReplayTimeline.vue +142 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/SessionReplayTimeline.webtest.ts +242 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/TestRunComposer.vue +97 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/ToolCatalogPanel.vue +100 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/ToolCatalogPanel.webtest.ts +55 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/ToolRuleDetail.vue +81 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/WorkbenchSessionList.vue +57 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/catalog-presenter.ts +20 -0
- package/template/apps/web/src/pages/system/components/agent-workbench/replay-presenter.ts +94 -0
- package/template/apps/web/src/pages/system/composables/useAgentWorkbench.ts +298 -0
- package/template/apps/web/src/pages/system/composables/useAgentWorkbench.webtest.ts +164 -0
- package/template/apps/web/src/pages/system/composables/useSessionReplay.ts +48 -0
- package/template/apps/web/src/router/index.ts +4 -0
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
- package/template/apps/web/src/services/agent-admin-api.ts +155 -0
- package/template/apps/web/src/services/agent-admin-api.webtest.ts +120 -0
- package/template/apps/web/src/services/agent-workbench-event.ts +60 -0
- package/template/docs/architecture/agent-harness.md +4 -0
- package/template/docs/architecture/log-governance-implementation.md +20 -3
- package/template/docs/architecture/log-governance-plan.md +2 -2
- package/template/docs/platform/agent-runtime.md +18 -0
- package/template/packages/modules/src/ai-operation-composition.ts +9 -0
- package/template/packages/modules/src/ai-operation-visibility.test.ts +33 -0
- package/template/packages/modules/src/manifest.ts +2 -0
- package/template/packages/modules/src/system-admin-routes.ts +16 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +7 -1
- package/template/packages/modules/src/system-agent-admin-bindings.ts +42 -0
- package/template/packages/modules/src/system-agent-admin-menus.ts +26 -0
- package/template/packages/modules/src/system-agent-admin-registration.test.ts +52 -0
- package/template/packages/modules/src/system-agent-audit-actions.ts +5 -0
- package/template/packages/modules/src/system-ai-api-bindings.ts +2 -0
- package/template/packages/modules/src/system-audit-actions.ts +25 -0
- package/template/packages/modules/src/system-features.ts +19 -0
- package/template/packages/modules/src/system-module-catalog.ts +14 -12
- package/template/packages/modules/src/system-module-ownership.ts +12 -0
- package/template/packages/modules/src/system-modules.ts +2 -0
- package/template/packages/modules/src/system-permissions.ts +5 -0
- package/template/packages/server-sdk/index.ts +2 -0
- package/template/packages/server-sdk/src/agent-session-provider.ts +19 -0
- package/template/packages/shared/index.ts +2 -0
- package/template/packages/shared/src/agent-admin-catalog.ts +73 -0
- 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
|
|
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(
|
|
109
|
-
|
|
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(
|
|
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(
|
|
153
|
-
|
|
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(
|
|
192
|
-
|
|
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 {
|
|
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
|
|
66
|
-
|
|
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
|
-
|
|
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({
|
|
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 {
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
}
|