create-windy 0.6.14 → 0.6.15
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 +215 -24
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +58 -8
- package/template/.windy-template.json +2 -2
- package/template/apps/server/src/agent/client-session/attempt-authority.test.ts +162 -0
- package/template/apps/server/src/agent/client-session/attempt-authority.ts +191 -0
- package/template/apps/server/src/agent/client-session/bootstrap.ts +27 -11
- package/template/apps/server/src/agent/client-session/completion.ts +75 -24
- package/template/apps/server/src/agent/client-session/contracts.ts +23 -0
- package/template/apps/server/src/agent/client-session/creation.ts +103 -0
- package/template/apps/server/src/agent/client-session/digest.ts +11 -1
- package/template/apps/server/src/agent/client-session/drizzle-records.ts +150 -0
- package/template/apps/server/src/agent/client-session/drizzle-repository.integration.test.ts +30 -3
- package/template/apps/server/src/agent/client-session/drizzle-repository.ts +23 -132
- package/template/apps/server/src/agent/client-session/finalization.ts +99 -0
- package/template/apps/server/src/agent/client-session/host-auth.ts +22 -10
- package/template/apps/server/src/agent/client-session/host-revocation-composition.test.ts +77 -0
- package/template/apps/server/src/agent/client-session/host-revocation-composition.ts +28 -0
- package/template/apps/server/src/agent/client-session/launch-authority.contract.test.ts +21 -0
- package/template/apps/server/src/agent/client-session/manifest-admission.ts +11 -0
- package/template/apps/server/src/agent/client-session/service.test-support.ts +22 -0
- package/template/apps/server/src/agent/client-session/service.ts +104 -134
- package/template/apps/server/src/agent/client-session/terminal-digest.test.ts +23 -0
- package/template/apps/server/src/agent/client-workflow-artifact/active-blob-references.ts +8 -0
- package/template/apps/server/src/agent/client-workflow-artifact/artifact-builder.ts +60 -0
- package/template/apps/server/src/agent/client-workflow-artifact/authority-current.ts +59 -0
- package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.test.ts +189 -0
- package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.ts +153 -0
- package/template/apps/server/src/agent/client-workflow-artifact/bootstrap.ts +77 -0
- package/template/apps/server/src/agent/client-workflow-artifact/cancellation.ts +47 -0
- package/template/apps/server/src/agent/client-workflow-artifact/completed-recovery.ts +31 -0
- package/template/apps/server/src/agent/client-workflow-artifact/contracts.ts +104 -0
- package/template/apps/server/src/agent/client-workflow-artifact/drizzle-locks.ts +30 -0
- package/template/apps/server/src/agent/client-workflow-artifact/drizzle-mapper.ts +138 -0
- package/template/apps/server/src/agent/client-workflow-artifact/drizzle-repository.ts +346 -0
- package/template/apps/server/src/agent/client-workflow-artifact/error.ts +21 -0
- package/template/apps/server/src/agent/client-workflow-artifact/handler.ts +167 -0
- package/template/apps/server/src/agent/client-workflow-artifact/host-revocation.ts +29 -0
- package/template/apps/server/src/agent/client-workflow-artifact/in-memory-repository.ts +232 -0
- package/template/apps/server/src/agent/client-workflow-artifact/job-port.ts +101 -0
- package/template/apps/server/src/agent/client-workflow-artifact/presentation.ts +41 -0
- package/template/apps/server/src/agent/client-workflow-artifact/recovery.contract.test.ts +253 -0
- package/template/apps/server/src/agent/client-workflow-artifact/release-binding.ts +85 -0
- package/template/apps/server/src/agent/client-workflow-artifact/repository.test.ts +204 -0
- package/template/apps/server/src/agent/client-workflow-artifact/repository.ts +97 -0
- package/template/apps/server/src/agent/client-workflow-artifact/routes-handler.contract.test.ts +201 -0
- package/template/apps/server/src/agent/client-workflow-artifact/routes.ts +161 -0
- package/template/apps/server/src/agent/client-workflow-artifact/runtime-composition.ts +101 -0
- package/template/apps/server/src/agent/client-workflow-artifact/scope-authority.ts +39 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service-authorization.ts +117 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service-support.ts +28 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service.contract.test.ts +345 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service.ts +350 -0
- package/template/apps/server/src/agent/client-workflow-artifact/session-boundary.ts +39 -0
- package/template/apps/server/src/agent/client-workflow-artifact/source-access.contract.test.ts +98 -0
- package/template/apps/server/src/agent/client-workflow-artifact/source-access.ts +80 -0
- package/template/apps/server/src/agent/client-workflow-artifact/takeover.ts +122 -0
- package/template/apps/server/src/agent/client-workflow-artifact/task-authority.contract.test.ts +97 -0
- package/template/apps/server/src/agent/client-workflow-artifact/task-authority.ts +147 -0
- package/template/apps/server/src/agent/client-workflow-artifact/terminal-authority.contract.test.ts +100 -0
- package/template/apps/server/src/agent/client-workflow-artifact/terminal-replay.ts +31 -0
- package/template/apps/server/src/agent/client-workflow-artifact/terminal-seal.ts +211 -0
- package/template/apps/server/src/agent/client-workflow-artifact/workflow-artifact.test-support.ts +173 -0
- package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.contract.test.ts +254 -0
- package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.ts +285 -0
- package/template/apps/server/src/agent/run-facade.test-support.ts +17 -4
- package/template/apps/server/src/agent/run-facade.test.ts +16 -0
- package/template/apps/server/src/agent/run-session-authorization.ts +12 -10
- package/template/apps/server/src/agent/runtime-bootstrap-input.ts +5 -1
- package/template/apps/server/src/agent/runtime-bootstrap.ts +33 -33
- package/template/apps/server/src/agent/runtime-public-ports.ts +40 -0
- package/template/apps/server/src/agent/session-invalidation/contracts.ts +25 -26
- package/template/apps/server/src/agent/session-projection-access.ts +31 -19
- package/template/apps/server/src/agent/unavailable-routes.ts +21 -0
- package/template/apps/server/src/agent/workflow-runtime-bootstrap.ts +2 -0
- package/template/apps/server/src/agent-workflow/artifact-source-authorization.ts +12 -6
- package/template/apps/server/src/agent-workflow/command-facade.ts +70 -11
- package/template/apps/server/src/agent-workflow/drizzle-artifact-port.ts +9 -9
- package/template/apps/server/src/agent-workflow/drizzle-delegation-completion.ts +1 -1
- package/template/apps/server/src/agent-workflow/platform-node-adapters.ts +14 -6
- package/template/apps/server/src/agent-workflow/project-launch-authority.contract.test.ts +53 -0
- package/template/apps/server/src/agent-workflow/project-launch-authority.ts +92 -0
- package/template/apps/server/src/agent-workflow/runtime-composition.ts +30 -14
- package/template/apps/server/src/app/agent-runtime-composition.ts +6 -1
- package/template/apps/server/src/app/client-agent-workflow-artifact-binding.ts +14 -0
- package/template/apps/server/src/application-services.ts +6 -0
- package/template/apps/server/src/data-access/scope-containment.test.ts +31 -0
- package/template/apps/server/src/data-access/scope-containment.ts +48 -0
- package/template/apps/server/src/jobs/artifact-publication.test.ts +192 -0
- package/template/apps/server/src/jobs/artifact-publication.ts +56 -0
- package/template/apps/server/src/jobs/artifact-publisher.ts +101 -0
- package/template/apps/server/src/jobs/drizzle-artifact.ts +73 -1
- package/template/apps/server/src/jobs/drizzle-execution-repository.ts +14 -1
- package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +65 -0
- package/template/apps/server/src/jobs/drizzle-repository.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/execution-repository.ts +28 -0
- package/template/apps/server/src/jobs/execution.test.ts +3 -1
- package/template/apps/server/src/jobs/in-memory-execution.ts +16 -0
- package/template/apps/server/src/jobs/in-memory-lease.ts +45 -1
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +2 -0
- package/template/apps/server/src/jobs/postgres-cleanup-contract.test.ts +58 -0
- package/template/apps/server/src/jobs/worker.ts +8 -14
- package/template/apps/server/src/module-host/background-ai-unavailable.ts +18 -0
- package/template/apps/server/src/module-host/durable-context.ts +19 -22
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +80 -1
- package/template/apps/server/src/module-host/implementation-completeness.ts +28 -1
- package/template/apps/server/src/module-host/request-context.ts +32 -8
- package/template/apps/server/src/module-host/storage-port.ts +3 -0
- package/template/apps/server/src/module-host/workflow-input-artifact-port.test.ts +104 -0
- package/template/apps/server/src/module-host/workflow-input-artifact-port.ts +229 -0
- package/template/apps/server/src/module-host/workflow-input-materialization.test.ts +163 -0
- package/template/apps/server/src/module-host/workflow-input-materialization.ts +161 -0
- package/template/apps/server/src/workflow/drizzle-repository.integration.test.ts +1 -0
- package/template/docs/architecture/agent-workflow.md +13 -2
- package/template/docs/architecture/durable-jobs.md +7 -0
- package/template/docs/operations/agent-session-memory.md +2 -1
- package/template/docs/operations/agent-session-troubleshooting.md +87 -2
- package/template/docs/operations/agent-workflow-recovery.md +25 -14
- package/template/docs/platform/client-agent-session.md +148 -2
- package/template/packages/ai-client/package.json +2 -1
- package/template/packages/ai-client/src/client-agent/index.ts +1 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/contracts.ts +85 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/http.test.ts +49 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/http.ts +82 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/index.ts +3 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.test.ts +154 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.ts +332 -0
- package/template/packages/database/drizzle/0062_client_agent_workflow_artifact.sql +63 -0
- package/template/packages/database/drizzle/meta/0062_snapshot.json +14839 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/agent-runtime-schema.test.ts +177 -0
- package/template/packages/database/src/schema/client-agent-workflow-artifact.ts +158 -0
- package/template/packages/database/src/schema/index.ts +1 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs/src/handlers.test.ts +10 -0
- package/template/packages/jobs/src/handlers.ts +28 -11
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-client-execution-policy.ts +6 -0
- package/template/packages/modules/src/manifest.ts +2 -0
- package/template/packages/modules/src/system-agent-audit-actions.ts +3 -0
- package/template/packages/modules/src/system-ai-api-bindings.ts +15 -0
- package/template/packages/modules/src/system-audit-actions.ts +15 -0
- package/template/packages/server-sdk/index.ts +52 -0
- package/template/packages/server-sdk/package.json +5 -1
- package/template/packages/server-sdk/src/client-agent-session.ts +13 -1
- package/template/packages/server-sdk/src/client-agent-workflow-artifact.contract.test.ts +113 -0
- package/template/packages/server-sdk/src/client-agent-workflow-artifact.ts +183 -0
- package/template/packages/server-sdk/src/durable-handler.ts +7 -0
- package/template/packages/server-sdk/src/request-context.ts +7 -0
- package/template/packages/server-sdk/src/workflow-input-artifact-port.ts +104 -0
- package/template/packages/server-sdk/src/workflow-input-artifact-validation.contract.test.ts +124 -0
- package/template/packages/server-sdk/src/workflow-input-artifact-validation.ts +347 -0
- package/template/packages/server-sdk/src/workflow-input-materialization-handler.ts +43 -0
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage/src/blob-store-put-once.test.ts +70 -0
- package/template/packages/storage/src/blob-store.ts +9 -0
- package/template/packages/storage/src/local-file-blob-put-once.ts +120 -0
- package/template/packages/storage/src/local-file-blob-store.ts +53 -1
- package/template/packages/storage/src/memory-blob-store.ts +73 -33
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
|
|
2
|
+
import type { RequestGuardContext } from "../../guards.js";
|
|
3
|
+
import type { RouteApp } from "../../system/routes.js";
|
|
4
|
+
import type { ClientAgentHostContext } from "../client-session/host-auth.js";
|
|
5
|
+
import { ClientAgentWorkflowArtifactError } from "./error.js";
|
|
6
|
+
import type { GovernedClientAgentWorkflowArtifactService } from "./service.js";
|
|
7
|
+
|
|
8
|
+
interface RouteContext {
|
|
9
|
+
readonly body?: unknown;
|
|
10
|
+
readonly request: Request;
|
|
11
|
+
readonly guardContext: RequestGuardContext;
|
|
12
|
+
readonly clientAgentHost: ClientAgentHostContext;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function registerClientAgentWorkflowArtifactRoutes(
|
|
16
|
+
app: RouteApp,
|
|
17
|
+
service: GovernedClientAgentWorkflowArtifactService,
|
|
18
|
+
) {
|
|
19
|
+
app.post("/agent/client-workflow-artifacts/claim", async (raw) => {
|
|
20
|
+
const context = raw as RouteContext;
|
|
21
|
+
try {
|
|
22
|
+
const body = object(context.body, [
|
|
23
|
+
"workflowRunId",
|
|
24
|
+
"clientBindingDigest",
|
|
25
|
+
]);
|
|
26
|
+
return await service.claim({
|
|
27
|
+
workflowRunId: text(body.workflowRunId),
|
|
28
|
+
clientBindingDigest: text(body.clientBindingDigest),
|
|
29
|
+
principal: context.clientAgentHost.principal,
|
|
30
|
+
guardContext: context.guardContext,
|
|
31
|
+
signal: context.request.signal,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return routeError(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.post("/agent/client-workflow-artifacts/advance", async (raw) => {
|
|
39
|
+
const context = raw as RouteContext;
|
|
40
|
+
try {
|
|
41
|
+
const body = object(context.body, [
|
|
42
|
+
"workId",
|
|
43
|
+
"claimToken",
|
|
44
|
+
"claimVersion",
|
|
45
|
+
"receiptIds",
|
|
46
|
+
"result",
|
|
47
|
+
]);
|
|
48
|
+
return await service.advance({
|
|
49
|
+
workId: text(body.workId),
|
|
50
|
+
claimToken: text(body.claimToken),
|
|
51
|
+
claimVersion: integer(body.claimVersion),
|
|
52
|
+
receiptIds: receiptIds(body.receiptIds),
|
|
53
|
+
...(body.result === undefined
|
|
54
|
+
? {}
|
|
55
|
+
: { result: body.result as ModuleAiJsonValue }),
|
|
56
|
+
principal: context.clientAgentHost.principal,
|
|
57
|
+
guardContext: context.guardContext,
|
|
58
|
+
signal: context.request.signal,
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return routeError(error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
app.post("/agent/client-workflow-artifacts/cancel", async (raw) => {
|
|
66
|
+
const context = raw as RouteContext;
|
|
67
|
+
try {
|
|
68
|
+
const body = object(context.body, [
|
|
69
|
+
"workId",
|
|
70
|
+
"claimToken",
|
|
71
|
+
"claimVersion",
|
|
72
|
+
]);
|
|
73
|
+
return await service.cancelClaim({
|
|
74
|
+
workId: text(body.workId),
|
|
75
|
+
claimToken: text(body.claimToken),
|
|
76
|
+
claimVersion: integer(body.claimVersion),
|
|
77
|
+
principal: context.clientAgentHost.principal,
|
|
78
|
+
guardContext: context.guardContext,
|
|
79
|
+
signal: context.request.signal,
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return routeError(error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function object(value: unknown, allowed: readonly string[]) {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
89
|
+
throw invalid();
|
|
90
|
+
}
|
|
91
|
+
const record = value as Record<string, unknown>;
|
|
92
|
+
if (Object.keys(record).some((key) => !allowed.includes(key)))
|
|
93
|
+
throw invalid();
|
|
94
|
+
return record;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function text(value: unknown) {
|
|
98
|
+
if (typeof value !== "string" || !value.trim() || value.length > 512) {
|
|
99
|
+
throw invalid();
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function integer(value: unknown) {
|
|
105
|
+
if (!Number.isSafeInteger(value) || Number(value) < 0) throw invalid();
|
|
106
|
+
return Number(value);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function receiptIds(value: unknown) {
|
|
110
|
+
if (
|
|
111
|
+
!Array.isArray(value) ||
|
|
112
|
+
value.length < 1 ||
|
|
113
|
+
value.length > 128 ||
|
|
114
|
+
value.some((item) => typeof item !== "string" || !item.trim()) ||
|
|
115
|
+
new Set(value).size !== value.length
|
|
116
|
+
) {
|
|
117
|
+
throw invalid();
|
|
118
|
+
}
|
|
119
|
+
return value as string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function invalid() {
|
|
123
|
+
return new ClientAgentWorkflowArtifactError(
|
|
124
|
+
"INVALID_INPUT",
|
|
125
|
+
"Client Agent Workflow 请求无效",
|
|
126
|
+
400,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function routeError(error: unknown) {
|
|
131
|
+
if (error instanceof ClientAgentWorkflowArtifactError) {
|
|
132
|
+
return Response.json(
|
|
133
|
+
{
|
|
134
|
+
code: error.code,
|
|
135
|
+
message: error.message,
|
|
136
|
+
safeCode: safeCode(error),
|
|
137
|
+
retryable: error.status >= 500,
|
|
138
|
+
},
|
|
139
|
+
{ status: error.status },
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return Response.json(
|
|
143
|
+
{
|
|
144
|
+
code: "INTERNAL_ERROR",
|
|
145
|
+
message: "Client Agent Workflow 调用失败",
|
|
146
|
+
safeCode: "INTERNAL_ERROR",
|
|
147
|
+
retryable: false,
|
|
148
|
+
},
|
|
149
|
+
{ status: 500 },
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function safeCode(error: ClientAgentWorkflowArtifactError) {
|
|
154
|
+
if (error.code === "NOT_FOUND") return "WORK_NOT_FOUND";
|
|
155
|
+
if (error.code === "CANCELLED") return "WORK_CANCELLED";
|
|
156
|
+
if (error.code === "ARTIFACT_TOO_LARGE") return "ARTIFACT_REJECTED";
|
|
157
|
+
if (error.code === "PROVIDER_FAILED") return "HOST_UNAVAILABLE";
|
|
158
|
+
if (error.code === "RECEIPT_REJECTED") return "RECEIPT_REJECTED";
|
|
159
|
+
if (error.code === "CONFLICT") return "CLAIM_CONFLICT";
|
|
160
|
+
return "INTERNAL_ERROR";
|
|
161
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ModuleAuthoritativeSourceAdapterRegistration } from "../context-memory/authoritative-source-registry.js";
|
|
2
|
+
import type { AgentPlatformRuntimeInput } from "../runtime-bootstrap-input.js";
|
|
3
|
+
import type { ClientSessionRepository } from "../client-session/repository.js";
|
|
4
|
+
import type { GovernedClientAgentSessionService } from "../client-session/service.js";
|
|
5
|
+
import type { ClientSessionAttemptAuthorityBinding } from "../client-session/attempt-authority.js";
|
|
6
|
+
import type { StaticRouteAuthorizer } from "../../module-host/static-route-authorization.js";
|
|
7
|
+
import type { RouteApp } from "../../system/routes.js";
|
|
8
|
+
import { registerUnavailableClientAgentWorkflowArtifactRoutes } from "../unavailable-routes.js";
|
|
9
|
+
import { createClientAgentWorkflowArtifactRuntime } from "./bootstrap.js";
|
|
10
|
+
import { DrizzleClientAgentWorkflowArtifactRepository } from "./drizzle-repository.js";
|
|
11
|
+
|
|
12
|
+
export function attachClientAgentWorkflowArtifactRuntime(input: {
|
|
13
|
+
readonly platform: AgentPlatformRuntimeInput;
|
|
14
|
+
readonly aiRouteApp: RouteApp;
|
|
15
|
+
readonly hostApp?: RouteApp;
|
|
16
|
+
readonly routeAuthorizer: StaticRouteAuthorizer;
|
|
17
|
+
readonly sessions?: GovernedClientAgentSessionService;
|
|
18
|
+
readonly sessionRepository?: ClientSessionRepository;
|
|
19
|
+
readonly attemptAuthority?: ClientSessionAttemptAuthorityBinding;
|
|
20
|
+
}) {
|
|
21
|
+
if (!input.attemptAuthority) {
|
|
22
|
+
registerUnavailableClientAgentWorkflowArtifactRoutes(input.aiRouteApp);
|
|
23
|
+
return {
|
|
24
|
+
authoritativeSources:
|
|
25
|
+
[] as readonly ModuleAuthoritativeSourceAdapterRegistration[],
|
|
26
|
+
revokeHost: undefined,
|
|
27
|
+
dispose: undefined,
|
|
28
|
+
bindTaskArtifacts: undefined,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const authorityRepository = input.platform.db
|
|
32
|
+
? new DrizzleClientAgentWorkflowArtifactRepository(input.platform.db)
|
|
33
|
+
: undefined;
|
|
34
|
+
let workflowRuntimeReady = false;
|
|
35
|
+
const classify = (decision: "ordinary" | "current" | "revoked") =>
|
|
36
|
+
!workflowRuntimeReady && decision === "current" ? "revoked" : decision;
|
|
37
|
+
const authorityInput = (
|
|
38
|
+
facts: Parameters<ClientSessionAttemptAuthorityBinding["check"]>[0],
|
|
39
|
+
now: number,
|
|
40
|
+
) => ({
|
|
41
|
+
clientSessionId: facts.sessionId,
|
|
42
|
+
actorId: facts.actorId,
|
|
43
|
+
operationKey: facts.operationKey,
|
|
44
|
+
clientBindingDigest: facts.clientBindingDigest,
|
|
45
|
+
hostSessionDigest: facts.hostSessionDigest,
|
|
46
|
+
now,
|
|
47
|
+
});
|
|
48
|
+
const unbindAuthority = input.attemptAuthority.bind({
|
|
49
|
+
async check(facts) {
|
|
50
|
+
if (!authorityRepository) return "ordinary";
|
|
51
|
+
return classify(
|
|
52
|
+
await authorityRepository.authorizeClientSessionAttempt(
|
|
53
|
+
authorityInput(facts, Date.now()),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
async checkTransaction(transaction, facts, now) {
|
|
58
|
+
if (!authorityRepository) return "ordinary";
|
|
59
|
+
return classify(
|
|
60
|
+
await authorityRepository.authorizeClientSessionAttemptInTransaction(
|
|
61
|
+
transaction,
|
|
62
|
+
authorityInput(facts, now),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const runtime = createClientAgentWorkflowArtifactRuntime({
|
|
68
|
+
db: input.platform.db,
|
|
69
|
+
hostApp: input.hostApp,
|
|
70
|
+
routeAuthorizer: input.routeAuthorizer,
|
|
71
|
+
bindings: input.platform.clientAgentWorkflowArtifactBindings,
|
|
72
|
+
sessions: input.sessions,
|
|
73
|
+
sessionRepository: input.sessionRepository,
|
|
74
|
+
jobs: input.platform.jobs,
|
|
75
|
+
jobRegistry: input.platform.jobRegistry,
|
|
76
|
+
blobs: input.platform.storage.blobStore,
|
|
77
|
+
});
|
|
78
|
+
if (!runtime) {
|
|
79
|
+
registerUnavailableClientAgentWorkflowArtifactRoutes(input.aiRouteApp);
|
|
80
|
+
return {
|
|
81
|
+
authoritativeSources:
|
|
82
|
+
[] as readonly ModuleAuthoritativeSourceAdapterRegistration[],
|
|
83
|
+
revokeHost: undefined,
|
|
84
|
+
dispose: unbindAuthority,
|
|
85
|
+
bindTaskArtifacts: undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
workflowRuntimeReady = true;
|
|
89
|
+
return {
|
|
90
|
+
authoritativeSources: [
|
|
91
|
+
{
|
|
92
|
+
moduleKey: "system.agent-workflow",
|
|
93
|
+
access: runtime.sourceAccess,
|
|
94
|
+
},
|
|
95
|
+
] satisfies readonly ModuleAuthoritativeSourceAdapterRegistration[],
|
|
96
|
+
revokeHost: (principal: Parameters<typeof runtime.service.revokeHost>[0]) =>
|
|
97
|
+
runtime.service.revokeHost(principal).then(() => {}),
|
|
98
|
+
dispose: unbindAuthority,
|
|
99
|
+
bindTaskArtifacts: runtime.bindTaskArtifacts,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DurableJobSnapshot } from "@southwind-ai/jobs";
|
|
2
|
+
import type { AuthorizationScopeSnapshot } from "@southwind-ai/shared";
|
|
3
|
+
import { authorizationScopeContains } from "../../data-access/scope-containment.js";
|
|
4
|
+
import { hashAuthorizationScope } from "../../data-access/scope-snapshot.js";
|
|
5
|
+
import type { ClientAgentWorkflowArtifactRecord } from "./contracts.js";
|
|
6
|
+
import { clientAgentWorkflowArtifactTaskKey } from "./contracts.js";
|
|
7
|
+
|
|
8
|
+
/** Reads the immutable Run authorization from its real Durable Job, never from a client claim. */
|
|
9
|
+
export function requireClientWorkflowFrozenScope(
|
|
10
|
+
record: ClientAgentWorkflowArtifactRecord,
|
|
11
|
+
snapshot: DurableJobSnapshot | undefined,
|
|
12
|
+
): AuthorizationScopeSnapshot {
|
|
13
|
+
const job = snapshot?.job;
|
|
14
|
+
if (
|
|
15
|
+
!job ||
|
|
16
|
+
job.id !== record.durableJobId ||
|
|
17
|
+
job.handlerKey !== clientAgentWorkflowArtifactTaskKey ||
|
|
18
|
+
job.requestedBy !== record.actorId ||
|
|
19
|
+
!job.authorizationScope ||
|
|
20
|
+
`sha256:${hashAuthorizationScope(job.authorizationScope)}` !==
|
|
21
|
+
record.scopeDigest
|
|
22
|
+
) {
|
|
23
|
+
throw new Error("CLIENT_WORKFLOW_FROZEN_SCOPE_INVALID");
|
|
24
|
+
}
|
|
25
|
+
return structuredClone(job.authorizationScope);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Equality is only a fast path; a current wider scope may contain the frozen Run scope. */
|
|
29
|
+
export function currentClientWorkflowScopeContains(input: {
|
|
30
|
+
readonly current: AuthorizationScopeSnapshot;
|
|
31
|
+
readonly currentDigest: string;
|
|
32
|
+
readonly frozen: AuthorizationScopeSnapshot;
|
|
33
|
+
readonly frozenDigest: string;
|
|
34
|
+
}): boolean {
|
|
35
|
+
return (
|
|
36
|
+
input.currentDigest === input.frozenDigest ||
|
|
37
|
+
authorizationScopeContains(input.current, input.frozen)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ModuleClientAgentHostPrincipal } from "@southwind-ai/server-sdk";
|
|
2
|
+
import type { RequestGuardContext } from "../../guards.js";
|
|
3
|
+
import { captureAuthorizationScope } from "../../data-access/scope-snapshot.js";
|
|
4
|
+
import type { ClientSessionRepository } from "../client-session/repository.js";
|
|
5
|
+
import { digestText } from "../client-session/digest.js";
|
|
6
|
+
import { hostSessionDigest } from "../client-session/host-auth.js";
|
|
7
|
+
import type { ClientAgentWorkflowArtifactRecord } from "./contracts.js";
|
|
8
|
+
import { clientWorkflowPayloadOf } from "./presentation.js";
|
|
9
|
+
import type { ClientAgentWorkflowArtifactRepository } from "./repository.js";
|
|
10
|
+
import { sameWorkflowArtifactIdentity } from "./repository.js";
|
|
11
|
+
import {
|
|
12
|
+
requiredWorkflowArtifactText as required,
|
|
13
|
+
workflowArtifactActor as actor,
|
|
14
|
+
workflowArtifactHidden as hidden,
|
|
15
|
+
workflowArtifactInvalid as invalid,
|
|
16
|
+
} from "./service-support.js";
|
|
17
|
+
import type { ClientAgentWorkflowFactsLoader } from "./workflow-loader.js";
|
|
18
|
+
import { currentClientWorkflowScopeContains } from "./scope-authority.js";
|
|
19
|
+
|
|
20
|
+
export async function requireClientWorkflowClaim(input: {
|
|
21
|
+
workId: string;
|
|
22
|
+
claimVersion: number;
|
|
23
|
+
claimToken: string;
|
|
24
|
+
principal: ModuleClientAgentHostPrincipal;
|
|
25
|
+
guardContext: RequestGuardContext;
|
|
26
|
+
repository: ClientAgentWorkflowArtifactRepository;
|
|
27
|
+
sessionRepository: ClientSessionRepository;
|
|
28
|
+
now: number;
|
|
29
|
+
}) {
|
|
30
|
+
if (
|
|
31
|
+
!input.claimToken.startsWith("cawc_") ||
|
|
32
|
+
!Number.isSafeInteger(input.claimVersion) ||
|
|
33
|
+
input.claimVersion < 1
|
|
34
|
+
) {
|
|
35
|
+
throw invalid("领取凭据无效");
|
|
36
|
+
}
|
|
37
|
+
const current = await input.repository.find(required(input.workId));
|
|
38
|
+
if (
|
|
39
|
+
!current ||
|
|
40
|
+
current.actorId !== actor(input.guardContext) ||
|
|
41
|
+
input.principal.actorId !== actor(input.guardContext) ||
|
|
42
|
+
current.claimGeneration !== input.claimVersion ||
|
|
43
|
+
current.claimTokenHash !== digestText(input.claimToken) ||
|
|
44
|
+
current.installationId !== input.principal.installationId ||
|
|
45
|
+
current.hostSessionDigest !== hostSessionDigest(input.principal) ||
|
|
46
|
+
!current.clientSessionId ||
|
|
47
|
+
!["claimed", "completed", "cancelled"].includes(current.status)
|
|
48
|
+
) {
|
|
49
|
+
throw hidden();
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
current.status === "claimed" &&
|
|
53
|
+
(!current.claimExpiresAt || current.claimExpiresAt <= input.now)
|
|
54
|
+
) {
|
|
55
|
+
const session = await input.sessionRepository.find(current.clientSessionId);
|
|
56
|
+
if (session?.status !== "completed") throw hidden();
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function requireCurrentClientWorkflowArtifact(input: {
|
|
62
|
+
record: ClientAgentWorkflowArtifactRecord;
|
|
63
|
+
context: RequestGuardContext;
|
|
64
|
+
signal: AbortSignal;
|
|
65
|
+
loader: ClientAgentWorkflowFactsLoader;
|
|
66
|
+
}) {
|
|
67
|
+
const facts = await input.loader.load({
|
|
68
|
+
jobId: input.record.durableJobId,
|
|
69
|
+
payload: clientWorkflowPayloadOf(input.record),
|
|
70
|
+
signal: input.signal,
|
|
71
|
+
});
|
|
72
|
+
if (!sameWorkflowArtifactIdentity(input.record, facts.record)) throw hidden();
|
|
73
|
+
requireCurrentActor(input.record, input.context, facts.frozenScope);
|
|
74
|
+
return facts;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function requireTerminalClientWorkflowArtifact(input: {
|
|
78
|
+
record: ClientAgentWorkflowArtifactRecord;
|
|
79
|
+
context: RequestGuardContext;
|
|
80
|
+
signal: AbortSignal;
|
|
81
|
+
loader: ClientAgentWorkflowFactsLoader;
|
|
82
|
+
}) {
|
|
83
|
+
try {
|
|
84
|
+
const facts = await input.loader.loadTerminal({
|
|
85
|
+
record: input.record,
|
|
86
|
+
signal: input.signal,
|
|
87
|
+
});
|
|
88
|
+
requireCurrentActor(input.record, input.context, facts.frozenScope);
|
|
89
|
+
return facts;
|
|
90
|
+
} catch {
|
|
91
|
+
throw hidden();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requireCurrentActor(
|
|
96
|
+
record: ClientAgentWorkflowArtifactRecord,
|
|
97
|
+
context: RequestGuardContext,
|
|
98
|
+
frozenScope: import("@southwind-ai/shared").AuthorizationScopeSnapshot,
|
|
99
|
+
) {
|
|
100
|
+
try {
|
|
101
|
+
const scope = captureAuthorizationScope(context.actor.scopeContext);
|
|
102
|
+
if (
|
|
103
|
+
context.actor.id !== record.actorId ||
|
|
104
|
+
!context.actor.permissionKeys.includes("system.agent-workflow.manage") ||
|
|
105
|
+
!currentClientWorkflowScopeContains({
|
|
106
|
+
current: scope.snapshot,
|
|
107
|
+
currentDigest: `sha256:${scope.hash}`,
|
|
108
|
+
frozen: frozenScope,
|
|
109
|
+
frozenDigest: record.scopeDigest,
|
|
110
|
+
})
|
|
111
|
+
) {
|
|
112
|
+
throw hidden();
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
throw hidden();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RequestGuardContext } from "../../guards.js";
|
|
2
|
+
import { ClientAgentWorkflowArtifactError } from "./error.js";
|
|
3
|
+
|
|
4
|
+
export function workflowArtifactActor(context: RequestGuardContext) {
|
|
5
|
+
if (context.actor.type === "anonymous") throw workflowArtifactHidden();
|
|
6
|
+
return context.actor.id;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function requiredWorkflowArtifactText(value: string) {
|
|
10
|
+
if (!value.trim()) throw workflowArtifactInvalid("必填字段不能为空");
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function workflowArtifactHidden() {
|
|
15
|
+
return new ClientAgentWorkflowArtifactError(
|
|
16
|
+
"NOT_FOUND",
|
|
17
|
+
"Workflow Client Agent 任务不存在或不可访问",
|
|
18
|
+
404,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function workflowArtifactInvalid(message: string) {
|
|
23
|
+
return new ClientAgentWorkflowArtifactError("INVALID_INPUT", message, 400);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function workflowArtifactConflict(message: string) {
|
|
27
|
+
return new ClientAgentWorkflowArtifactError("CONFLICT", message, 409);
|
|
28
|
+
}
|