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,189 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
CLIENT_AGENT_WORKFLOW_ARTIFACT_BINDING_LIMITS as limits,
|
|
4
|
+
type ModuleClientAgentWorkflowArtifactProjectBinding,
|
|
5
|
+
} from "@southwind-ai/server-sdk";
|
|
6
|
+
import {
|
|
7
|
+
clientWorkflowBindingCommitment,
|
|
8
|
+
ClientAgentWorkflowArtifactBindingRegistry,
|
|
9
|
+
} from "./binding-registry.js";
|
|
10
|
+
|
|
11
|
+
test("Composition 启动时拒绝超出 DB/DTO 边界与路径型文件名", () => {
|
|
12
|
+
for (const binding of [
|
|
13
|
+
value({ workflowKey: "w".repeat(limits.workflowKey + 1) }),
|
|
14
|
+
value({ nodeId: "n".repeat(limits.nodeId + 1) }),
|
|
15
|
+
value({ operationKey: "o".repeat(limits.operationKey + 1) }),
|
|
16
|
+
value({ releaseDigest: `sha256:${"d".repeat(65)}` }),
|
|
17
|
+
value({
|
|
18
|
+
terminalResultAuthority: {
|
|
19
|
+
...value().terminalResultAuthority,
|
|
20
|
+
version: "v".repeat(limits.terminalAuthorityVersion + 1),
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
value({
|
|
24
|
+
terminalResultAuthority: {
|
|
25
|
+
...value().terminalResultAuthority,
|
|
26
|
+
digest: `sha256:${"e".repeat(65)}`,
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
value({
|
|
30
|
+
artifact: {
|
|
31
|
+
...value().artifact,
|
|
32
|
+
kind: "k".repeat(limits.artifactKind + 1),
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
value({
|
|
36
|
+
artifact: {
|
|
37
|
+
...value().artifact,
|
|
38
|
+
filename: `${"f".repeat(limits.artifactFilename)}x`,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
value({
|
|
42
|
+
artifact: {
|
|
43
|
+
...value().artifact,
|
|
44
|
+
retentionPolicyKey: "r".repeat(limits.retentionPolicyKey + 1),
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
value({
|
|
48
|
+
artifact: { ...value().artifact, maxByteSize: limits.maxByteSize + 1 },
|
|
49
|
+
}),
|
|
50
|
+
value({ artifact: { ...value().artifact, filename: "nested/out.json" } }),
|
|
51
|
+
value({ artifact: { ...value().artifact, filename: "nested\\out.json" } }),
|
|
52
|
+
value({ workflowKey: "workflow\0key" }),
|
|
53
|
+
value({ nodeId: "page-agent\0artifact" }),
|
|
54
|
+
value({ operationKey: "analysis\0page-agent" }),
|
|
55
|
+
value({ releaseDigest: `sha256:${"d".repeat(63)}\0` }),
|
|
56
|
+
value({
|
|
57
|
+
artifact: { ...value().artifact, kind: "analysis\0report" },
|
|
58
|
+
}),
|
|
59
|
+
invalidContentTypeBinding(),
|
|
60
|
+
value({
|
|
61
|
+
artifact: { ...value().artifact, filename: "analysis\0.json" },
|
|
62
|
+
}),
|
|
63
|
+
value({
|
|
64
|
+
artifact: {
|
|
65
|
+
...value().artifact,
|
|
66
|
+
retentionPolicyKey: "analysis\0case-artifact",
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
]) {
|
|
70
|
+
expect(
|
|
71
|
+
() => new ClientAgentWorkflowArtifactBindingRegistry([binding]),
|
|
72
|
+
).toThrow("CLIENT_AGENT_WORKFLOW_BINDING_INVALID");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function invalidContentTypeBinding(): ModuleClientAgentWorkflowArtifactProjectBinding {
|
|
77
|
+
const binding = value();
|
|
78
|
+
Object.defineProperty(binding.artifact, "contentType", {
|
|
79
|
+
value: "application/json\0",
|
|
80
|
+
enumerable: true,
|
|
81
|
+
});
|
|
82
|
+
return binding;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test("每个 Workflow 只允许一个固定 PageAgent Job binding", () => {
|
|
86
|
+
expect(
|
|
87
|
+
() =>
|
|
88
|
+
new ClientAgentWorkflowArtifactBindingRegistry([
|
|
89
|
+
value(),
|
|
90
|
+
value({ nodeId: "other-node" }),
|
|
91
|
+
]),
|
|
92
|
+
).toThrow("CLIENT_AGENT_WORKFLOW_BINDING_DUPLICATE");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("binding commitment 只承诺 authority version 与 digest", () => {
|
|
96
|
+
const first = value();
|
|
97
|
+
const second = value({
|
|
98
|
+
terminalResultAuthority: {
|
|
99
|
+
...first.terminalResultAuthority,
|
|
100
|
+
async canonicalize() {
|
|
101
|
+
return { changed: true };
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
clientTaskAuthority: {
|
|
105
|
+
version: "sharp.client-task.v1",
|
|
106
|
+
digest: `sha256:${"f".repeat(64)}`,
|
|
107
|
+
async build({ workflowInput }) {
|
|
108
|
+
return { taskKey: "sharp.full-review", input: workflowInput };
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
launchAuthority: {
|
|
112
|
+
version: "sharp.workflow-launch.v1",
|
|
113
|
+
digest: `sha256:${"a".repeat(64)}`,
|
|
114
|
+
async authorizeRelease() {},
|
|
115
|
+
async authorizeRun({ actorId }) {
|
|
116
|
+
return {
|
|
117
|
+
authorizationScope: {
|
|
118
|
+
tenantId: "single-tenant",
|
|
119
|
+
actorId,
|
|
120
|
+
grants: [{ scope: "self" }],
|
|
121
|
+
roleIds: ["role-analyst"],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
expect(clientWorkflowBindingCommitment(first)).toEqual(
|
|
128
|
+
clientWorkflowBindingCommitment(second),
|
|
129
|
+
);
|
|
130
|
+
expect(clientWorkflowBindingCommitment(first)).not.toEqual(
|
|
131
|
+
clientWorkflowBindingCommitment(
|
|
132
|
+
value({
|
|
133
|
+
terminalResultAuthority: {
|
|
134
|
+
...first.terminalResultAuthority,
|
|
135
|
+
version: "sharp.analysis-result.v2",
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
function value(
|
|
143
|
+
patch: Partial<ModuleClientAgentWorkflowArtifactProjectBinding> = {},
|
|
144
|
+
): ModuleClientAgentWorkflowArtifactProjectBinding {
|
|
145
|
+
return {
|
|
146
|
+
workflowKey: "analysis.cross-case.full-review",
|
|
147
|
+
nodeId: "page-agent-artifact",
|
|
148
|
+
operationKey: "analysis.cross-case.page-agent",
|
|
149
|
+
releaseDigest: `sha256:${"d".repeat(64)}`,
|
|
150
|
+
scopeContainmentContractVersion: 1,
|
|
151
|
+
terminalResultAuthority: {
|
|
152
|
+
version: "sharp.analysis-result.v1",
|
|
153
|
+
digest: `sha256:${"e".repeat(64)}`,
|
|
154
|
+
async canonicalize({ proposedResult }) {
|
|
155
|
+
return proposedResult;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
clientTaskAuthority: {
|
|
159
|
+
version: "sharp.client-task.v1",
|
|
160
|
+
digest: `sha256:${"f".repeat(64)}`,
|
|
161
|
+
async build({ workflowInput }) {
|
|
162
|
+
return { taskKey: "sharp.full-review", input: workflowInput };
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
launchAuthority: {
|
|
166
|
+
version: "sharp.workflow-launch.v1",
|
|
167
|
+
digest: `sha256:${"a".repeat(64)}`,
|
|
168
|
+
async authorizeRelease() {},
|
|
169
|
+
async authorizeRun({ actorId }) {
|
|
170
|
+
return {
|
|
171
|
+
authorizationScope: {
|
|
172
|
+
tenantId: "single-tenant",
|
|
173
|
+
actorId,
|
|
174
|
+
grants: [{ scope: "self" }],
|
|
175
|
+
roleIds: ["role-analyst"],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
artifact: {
|
|
181
|
+
kind: "analysis-report",
|
|
182
|
+
contentType: "application/json",
|
|
183
|
+
filename: "analysis.json",
|
|
184
|
+
retentionPolicyKey: "analysis.case-artifact",
|
|
185
|
+
maxByteSize: 4 * 1024 * 1024,
|
|
186
|
+
},
|
|
187
|
+
...patch,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLIENT_AGENT_WORKFLOW_ARTIFACT_BINDING_LIMITS as limits,
|
|
3
|
+
CLIENT_AGENT_WORKFLOW_SCOPE_CONTAINMENT_CONTRACT_VERSION,
|
|
4
|
+
} from "@southwind-ai/server-sdk";
|
|
5
|
+
import { validDigest } from "../client-session/digest.js";
|
|
6
|
+
import type { ClientAgentWorkflowArtifactBinding } from "./contracts.js";
|
|
7
|
+
|
|
8
|
+
export class ClientAgentWorkflowArtifactBindingRegistry {
|
|
9
|
+
readonly #bindings: ReadonlyMap<string, ClientAgentWorkflowArtifactBinding>;
|
|
10
|
+
|
|
11
|
+
constructor(bindings: readonly ClientAgentWorkflowArtifactBinding[]) {
|
|
12
|
+
const values = new Map<string, ClientAgentWorkflowArtifactBinding>();
|
|
13
|
+
const workflowKeys = new Set<string>();
|
|
14
|
+
for (const binding of bindings) {
|
|
15
|
+
assertBinding(binding);
|
|
16
|
+
const key = coordinate(binding.workflowKey, binding.nodeId);
|
|
17
|
+
if (values.has(key) || workflowKeys.has(binding.workflowKey)) {
|
|
18
|
+
throw new Error("CLIENT_AGENT_WORKFLOW_BINDING_DUPLICATE");
|
|
19
|
+
}
|
|
20
|
+
workflowKeys.add(binding.workflowKey);
|
|
21
|
+
values.set(key, freezeBinding(binding));
|
|
22
|
+
}
|
|
23
|
+
this.#bindings = values;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
require(workflowKey: string, nodeId: string) {
|
|
27
|
+
const binding = this.#bindings.get(coordinate(workflowKey, nodeId));
|
|
28
|
+
if (!binding) throw new Error("CLIENT_AGENT_WORKFLOW_BINDING_MISSING");
|
|
29
|
+
return binding;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
findWorkflow(workflowKey: string) {
|
|
33
|
+
return [...this.#bindings.values()].find(
|
|
34
|
+
(binding) => binding.workflowKey === workflowKey,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get size() {
|
|
39
|
+
return this.#bindings.size;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function coordinate(workflowKey: string, nodeId: string) {
|
|
44
|
+
return `${workflowKey}\0${nodeId}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertBinding(binding: ClientAgentWorkflowArtifactBinding) {
|
|
48
|
+
if (
|
|
49
|
+
!bounded(binding.workflowKey, limits.workflowKey) ||
|
|
50
|
+
!bounded(binding.nodeId, limits.nodeId) ||
|
|
51
|
+
!bounded(binding.operationKey, limits.operationKey) ||
|
|
52
|
+
!bounded(binding.releaseDigest, limits.releaseDigest) ||
|
|
53
|
+
!validDigest(binding.releaseDigest) ||
|
|
54
|
+
binding.scopeContainmentContractVersion !==
|
|
55
|
+
CLIENT_AGENT_WORKFLOW_SCOPE_CONTAINMENT_CONTRACT_VERSION ||
|
|
56
|
+
!bounded(
|
|
57
|
+
binding.terminalResultAuthority.version,
|
|
58
|
+
limits.terminalAuthorityVersion,
|
|
59
|
+
) ||
|
|
60
|
+
!bounded(
|
|
61
|
+
binding.terminalResultAuthority.digest,
|
|
62
|
+
limits.terminalAuthorityDigest,
|
|
63
|
+
) ||
|
|
64
|
+
!validDigest(binding.terminalResultAuthority.digest) ||
|
|
65
|
+
typeof binding.terminalResultAuthority.canonicalize !== "function" ||
|
|
66
|
+
!bounded(
|
|
67
|
+
binding.clientTaskAuthority.version,
|
|
68
|
+
limits.terminalAuthorityVersion,
|
|
69
|
+
) ||
|
|
70
|
+
!bounded(
|
|
71
|
+
binding.clientTaskAuthority.digest,
|
|
72
|
+
limits.terminalAuthorityDigest,
|
|
73
|
+
) ||
|
|
74
|
+
!validDigest(binding.clientTaskAuthority.digest) ||
|
|
75
|
+
typeof binding.clientTaskAuthority.build !== "function" ||
|
|
76
|
+
!bounded(
|
|
77
|
+
binding.launchAuthority.version,
|
|
78
|
+
limits.terminalAuthorityVersion,
|
|
79
|
+
) ||
|
|
80
|
+
!bounded(binding.launchAuthority.digest, limits.terminalAuthorityDigest) ||
|
|
81
|
+
!validDigest(binding.launchAuthority.digest) ||
|
|
82
|
+
typeof binding.launchAuthority.authorizeRelease !== "function" ||
|
|
83
|
+
typeof binding.launchAuthority.authorizeRun !== "function" ||
|
|
84
|
+
!bounded(binding.artifact.kind, limits.artifactKind) ||
|
|
85
|
+
!bounded(binding.artifact.contentType, limits.artifactContentType) ||
|
|
86
|
+
!bounded(binding.artifact.filename, limits.artifactFilename) ||
|
|
87
|
+
binding.artifact.filename.includes("/") ||
|
|
88
|
+
binding.artifact.filename.includes("\\") ||
|
|
89
|
+
!bounded(binding.artifact.retentionPolicyKey, limits.retentionPolicyKey) ||
|
|
90
|
+
binding.artifact.contentType !== "application/json" ||
|
|
91
|
+
!Number.isSafeInteger(binding.artifact.maxByteSize) ||
|
|
92
|
+
binding.artifact.maxByteSize < 1 ||
|
|
93
|
+
binding.artifact.maxByteSize > limits.maxByteSize
|
|
94
|
+
) {
|
|
95
|
+
throw new Error("CLIENT_AGENT_WORKFLOW_BINDING_INVALID");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function clientWorkflowBindingCommitment(
|
|
100
|
+
binding: ClientAgentWorkflowArtifactBinding,
|
|
101
|
+
) {
|
|
102
|
+
return {
|
|
103
|
+
workflowKey: binding.workflowKey,
|
|
104
|
+
nodeId: binding.nodeId,
|
|
105
|
+
operationKey: binding.operationKey,
|
|
106
|
+
releaseDigest: binding.releaseDigest,
|
|
107
|
+
scopeContainmentContractVersion: binding.scopeContainmentContractVersion,
|
|
108
|
+
terminalResultAuthority: {
|
|
109
|
+
version: binding.terminalResultAuthority.version,
|
|
110
|
+
digest: binding.terminalResultAuthority.digest,
|
|
111
|
+
},
|
|
112
|
+
clientTaskAuthority: {
|
|
113
|
+
version: binding.clientTaskAuthority.version,
|
|
114
|
+
digest: binding.clientTaskAuthority.digest,
|
|
115
|
+
},
|
|
116
|
+
launchAuthority: {
|
|
117
|
+
version: binding.launchAuthority.version,
|
|
118
|
+
digest: binding.launchAuthority.digest,
|
|
119
|
+
},
|
|
120
|
+
artifact: { ...binding.artifact },
|
|
121
|
+
} as const;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function freezeBinding(
|
|
125
|
+
binding: ClientAgentWorkflowArtifactBinding,
|
|
126
|
+
): ClientAgentWorkflowArtifactBinding {
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
...clientWorkflowBindingCommitment(binding),
|
|
129
|
+
terminalResultAuthority: Object.freeze({
|
|
130
|
+
...clientWorkflowBindingCommitment(binding).terminalResultAuthority,
|
|
131
|
+
canonicalize: binding.terminalResultAuthority.canonicalize,
|
|
132
|
+
}),
|
|
133
|
+
clientTaskAuthority: Object.freeze({
|
|
134
|
+
...clientWorkflowBindingCommitment(binding).clientTaskAuthority,
|
|
135
|
+
build: binding.clientTaskAuthority.build,
|
|
136
|
+
}),
|
|
137
|
+
launchAuthority: Object.freeze({
|
|
138
|
+
...clientWorkflowBindingCommitment(binding).launchAuthority,
|
|
139
|
+
authorizeRelease: binding.launchAuthority.authorizeRelease,
|
|
140
|
+
authorizeRun: binding.launchAuthority.authorizeRun,
|
|
141
|
+
}),
|
|
142
|
+
artifact: Object.freeze({ ...binding.artifact }),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function bounded(value: string, maxLength: number) {
|
|
147
|
+
return (
|
|
148
|
+
value.length > 0 &&
|
|
149
|
+
value.length <= maxLength &&
|
|
150
|
+
value === value.trim() &&
|
|
151
|
+
!value.includes(String.fromCharCode(0))
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ModuleClientAgentWorkflowArtifactProjectBinding } from "@southwind-ai/server-sdk";
|
|
2
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
|
+
import type { RouteApp } from "../../system/routes.js";
|
|
4
|
+
import type { DurableHandlerRegistry } from "@southwind-ai/jobs";
|
|
5
|
+
import type { BlobStore } from "@southwind-ai/storage";
|
|
6
|
+
import type { StaticRouteAuthorizer } from "../../module-host/static-route-authorization.js";
|
|
7
|
+
import { createManifestAuthorizedRouteApp } from "../../module-host/static-route-authorization.js";
|
|
8
|
+
import type { DurableJobService } from "../../jobs/service.js";
|
|
9
|
+
import type { GovernedClientAgentSessionService } from "../client-session/service.js";
|
|
10
|
+
import type { ClientSessionRepository } from "../client-session/repository.js";
|
|
11
|
+
import { DrizzleAgentWorkflowRepository } from "../../agent-workflow/drizzle-repository.js";
|
|
12
|
+
import { ClientAgentWorkflowArtifactBindingRegistry } from "./binding-registry.js";
|
|
13
|
+
import { DrizzleClientAgentWorkflowArtifactRepository } from "./drizzle-repository.js";
|
|
14
|
+
import { registerClientAgentWorkflowArtifactHandler } from "./handler.js";
|
|
15
|
+
import { registerClientAgentWorkflowArtifactRoutes } from "./routes.js";
|
|
16
|
+
import { GovernedClientAgentWorkflowArtifactService } from "./service.js";
|
|
17
|
+
import { ClientAgentWorkflowArtifactSourceAccess } from "./source-access.js";
|
|
18
|
+
import { ClientAgentWorkflowFactsLoader } from "./workflow-loader.js";
|
|
19
|
+
import { ClientAgentWorkflowTaskAuthority } from "./task-authority.js";
|
|
20
|
+
|
|
21
|
+
export function createClientAgentWorkflowArtifactRuntime(input: {
|
|
22
|
+
readonly db?: NodePgDatabase;
|
|
23
|
+
readonly hostApp?: RouteApp;
|
|
24
|
+
readonly routeAuthorizer: StaticRouteAuthorizer;
|
|
25
|
+
readonly bindings?: readonly ModuleClientAgentWorkflowArtifactProjectBinding[];
|
|
26
|
+
readonly sessions?: GovernedClientAgentSessionService;
|
|
27
|
+
readonly sessionRepository?: ClientSessionRepository;
|
|
28
|
+
readonly jobs: DurableJobService;
|
|
29
|
+
readonly jobRegistry: DurableHandlerRegistry;
|
|
30
|
+
readonly blobs: BlobStore;
|
|
31
|
+
}) {
|
|
32
|
+
if (
|
|
33
|
+
!input.db ||
|
|
34
|
+
!input.hostApp ||
|
|
35
|
+
!input.sessions ||
|
|
36
|
+
!input.sessionRepository ||
|
|
37
|
+
!input.bindings?.length
|
|
38
|
+
) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const bindings = new ClientAgentWorkflowArtifactBindingRegistry(
|
|
42
|
+
input.bindings,
|
|
43
|
+
);
|
|
44
|
+
const repository = new DrizzleClientAgentWorkflowArtifactRepository(input.db);
|
|
45
|
+
const loader = new ClientAgentWorkflowFactsLoader(
|
|
46
|
+
new DrizzleAgentWorkflowRepository(input.db),
|
|
47
|
+
input.jobs,
|
|
48
|
+
bindings,
|
|
49
|
+
);
|
|
50
|
+
const tasks = new ClientAgentWorkflowTaskAuthority();
|
|
51
|
+
const service = new GovernedClientAgentWorkflowArtifactService(
|
|
52
|
+
repository,
|
|
53
|
+
loader,
|
|
54
|
+
input.sessions,
|
|
55
|
+
input.sessionRepository,
|
|
56
|
+
input.blobs,
|
|
57
|
+
tasks,
|
|
58
|
+
);
|
|
59
|
+
registerClientAgentWorkflowArtifactHandler({
|
|
60
|
+
registry: input.jobRegistry,
|
|
61
|
+
service,
|
|
62
|
+
});
|
|
63
|
+
registerClientAgentWorkflowArtifactRoutes(
|
|
64
|
+
createManifestAuthorizedRouteApp(input.hostApp, input.routeAuthorizer),
|
|
65
|
+
service,
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
service,
|
|
69
|
+
sourceAccess: new ClientAgentWorkflowArtifactSourceAccess(
|
|
70
|
+
repository,
|
|
71
|
+
input.jobs,
|
|
72
|
+
),
|
|
73
|
+
bindTaskArtifacts: (
|
|
74
|
+
artifacts: import("../../agent-workflow/node-execution-ports.js").AgentWorkflowArtifactPort,
|
|
75
|
+
) => tasks.bind(artifacts),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { GovernedClientAgentSessionService } from "../client-session/service.js";
|
|
2
|
+
import type { ClientAgentWorkflowArtifactRecord } from "./contracts.js";
|
|
3
|
+
import type { ClientAgentWorkflowArtifactRepository } from "./repository.js";
|
|
4
|
+
import { clientWorkflowSessionCall } from "./session-boundary.js";
|
|
5
|
+
|
|
6
|
+
export async function cancelClientWorkflowArtifact(input: {
|
|
7
|
+
record: ClientAgentWorkflowArtifactRecord;
|
|
8
|
+
repository: ClientAgentWorkflowArtifactRepository;
|
|
9
|
+
sessions: GovernedClientAgentSessionService;
|
|
10
|
+
cause: string;
|
|
11
|
+
now: number;
|
|
12
|
+
}) {
|
|
13
|
+
const requested = await input.repository.requestCancel({
|
|
14
|
+
workId: input.record.workId,
|
|
15
|
+
now: input.now,
|
|
16
|
+
});
|
|
17
|
+
if (!requested || requested.status === "completed") return undefined;
|
|
18
|
+
if (requested.status === "cancelled") return requested;
|
|
19
|
+
if (requested.clientSessionId) {
|
|
20
|
+
await clientWorkflowSessionCall(
|
|
21
|
+
input.sessions.revokeSession(requested.clientSessionId, input.cause),
|
|
22
|
+
"revoke",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return input.repository.finalizeCancel({
|
|
26
|
+
workId: requested.workId,
|
|
27
|
+
now: input.now,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function recoverClientWorkflowCancellations(input: {
|
|
32
|
+
repository: ClientAgentWorkflowArtifactRepository;
|
|
33
|
+
sessions: GovernedClientAgentSessionService;
|
|
34
|
+
now: () => number;
|
|
35
|
+
}) {
|
|
36
|
+
const records = await input.repository.listCancellationRequested();
|
|
37
|
+
for (const record of records) {
|
|
38
|
+
await cancelClientWorkflowArtifact({
|
|
39
|
+
record,
|
|
40
|
+
repository: input.repository,
|
|
41
|
+
sessions: input.sessions,
|
|
42
|
+
cause: "workflow-cancel-recovery",
|
|
43
|
+
now: input.now(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return records.length;
|
|
47
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BlobStore } from "@southwind-ai/storage";
|
|
2
|
+
import type { ClientSessionRepository } from "../client-session/repository.js";
|
|
3
|
+
import type { ClientAgentWorkflowArtifactRecord } from "./contracts.js";
|
|
4
|
+
import type { ClientAgentWorkflowArtifactRepository } from "./repository.js";
|
|
5
|
+
import { workflowArtifactConflict as conflict } from "./service-support.js";
|
|
6
|
+
import { sealCompletedClientWorkflowSession } from "./terminal-seal.js";
|
|
7
|
+
import type { ClientAgentWorkflowFactsLoader } from "./workflow-loader.js";
|
|
8
|
+
|
|
9
|
+
export async function recoverCompletedClientWorkflowArtifact(input: {
|
|
10
|
+
current: ClientAgentWorkflowArtifactRecord;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
sessionRepository: ClientSessionRepository;
|
|
13
|
+
repository: ClientAgentWorkflowArtifactRepository;
|
|
14
|
+
loader: ClientAgentWorkflowFactsLoader;
|
|
15
|
+
blobs: BlobStore;
|
|
16
|
+
now: () => number;
|
|
17
|
+
}) {
|
|
18
|
+
const { current } = input;
|
|
19
|
+
if (current.status !== "claimed" || !current.clientSessionId) return current;
|
|
20
|
+
const session = await input.sessionRepository.find(current.clientSessionId);
|
|
21
|
+
if (!session || session.status !== "completed") return current;
|
|
22
|
+
if (!session.terminalDigest || !current.claimTokenHash) {
|
|
23
|
+
throw conflict("已完成 Client Agent Session 缺少稳定终态摘要");
|
|
24
|
+
}
|
|
25
|
+
return sealCompletedClientWorkflowSession({
|
|
26
|
+
...input,
|
|
27
|
+
session,
|
|
28
|
+
terminalRequestDigest: session.terminalDigest,
|
|
29
|
+
claimTokenHash: current.claimTokenHash,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleAiJsonValue,
|
|
3
|
+
ModuleClientAgentWorkflowArtifactProjectBinding,
|
|
4
|
+
ModuleClientAgentWorkflowExecutionTask,
|
|
5
|
+
} from "@southwind-ai/server-sdk";
|
|
6
|
+
import type { ArtifactRef } from "@southwind-ai/storage";
|
|
7
|
+
import type { AuthorizationScopeSnapshot } from "@southwind-ai/shared";
|
|
8
|
+
|
|
9
|
+
export const clientAgentWorkflowArtifactTaskKey =
|
|
10
|
+
"system.agent-workflow.client-agent-artifact";
|
|
11
|
+
export const clientAgentWorkflowArtifactSourceModule = "system.agent-workflow";
|
|
12
|
+
export const clientAgentWorkflowArtifactSourceKind = "client-agent-artifact";
|
|
13
|
+
|
|
14
|
+
export type ClientAgentWorkflowArtifactBinding =
|
|
15
|
+
ModuleClientAgentWorkflowArtifactProjectBinding;
|
|
16
|
+
|
|
17
|
+
export type ClientAgentWorkflowArtifactStatus =
|
|
18
|
+
| "pending"
|
|
19
|
+
| "claimed"
|
|
20
|
+
| "cancellation-requested"
|
|
21
|
+
| "completed"
|
|
22
|
+
| "cancelled";
|
|
23
|
+
|
|
24
|
+
export interface ClientAgentWorkflowArtifactRecord {
|
|
25
|
+
readonly workId: string;
|
|
26
|
+
readonly commandId: string;
|
|
27
|
+
readonly durableJobId: string;
|
|
28
|
+
readonly runId: string;
|
|
29
|
+
readonly nodeRunId: string;
|
|
30
|
+
readonly effectKind: "job";
|
|
31
|
+
readonly taskKey: typeof clientAgentWorkflowArtifactTaskKey;
|
|
32
|
+
readonly operationKey: string;
|
|
33
|
+
readonly bindingDigest: `sha256:${string}`;
|
|
34
|
+
readonly actorId: string;
|
|
35
|
+
readonly scopeDigest: `sha256:${string}`;
|
|
36
|
+
readonly authorizationSnapshotDigest: `sha256:${string}`;
|
|
37
|
+
readonly runPolicyDigest: `sha256:${string}`;
|
|
38
|
+
readonly requestDigest: `sha256:${string}`;
|
|
39
|
+
readonly inputDigest: `sha256:${string}`;
|
|
40
|
+
readonly effectGeneration: number;
|
|
41
|
+
readonly effectFenceTokenDigest: `sha256:${string}`;
|
|
42
|
+
readonly status: ClientAgentWorkflowArtifactStatus;
|
|
43
|
+
readonly claimGeneration: number;
|
|
44
|
+
readonly clientBindingDigest?: `sha256:${string}`;
|
|
45
|
+
readonly installationId?: string;
|
|
46
|
+
readonly hostSessionDigest?: `sha256:${string}`;
|
|
47
|
+
readonly clientSessionId?: string;
|
|
48
|
+
readonly claimTokenHash?: `sha256:${string}`;
|
|
49
|
+
readonly claimExpiresAt?: number;
|
|
50
|
+
readonly historySequence: number;
|
|
51
|
+
readonly historyHeadDigest?: `sha256:${string}`;
|
|
52
|
+
readonly terminalReceiptId?: string;
|
|
53
|
+
readonly terminalReceiptDigest?: `sha256:${string}`;
|
|
54
|
+
readonly terminalRequestDigest?: `sha256:${string}`;
|
|
55
|
+
readonly terminalResult?: Readonly<Record<string, unknown>>;
|
|
56
|
+
readonly artifact?: ClientAgentWorkflowStoredArtifact;
|
|
57
|
+
readonly cancelRequestedAt?: number;
|
|
58
|
+
readonly finishedAt?: number;
|
|
59
|
+
readonly version: number;
|
|
60
|
+
readonly createdAt: number;
|
|
61
|
+
readonly updatedAt: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ClientAgentWorkflowStoredArtifact {
|
|
65
|
+
readonly artifactRef: ArtifactRef;
|
|
66
|
+
readonly filename: string;
|
|
67
|
+
readonly contentType: string;
|
|
68
|
+
readonly byteSize: number;
|
|
69
|
+
readonly sha256: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ClientAgentWorkflowBeginFacts {
|
|
73
|
+
readonly record: ClientAgentWorkflowArtifactRecord;
|
|
74
|
+
readonly input: ModuleAiJsonValue;
|
|
75
|
+
readonly binding: ClientAgentWorkflowArtifactBinding;
|
|
76
|
+
/** Immutable authorization captured by the real Durable Job at Run launch. */
|
|
77
|
+
readonly frozenScope: AuthorizationScopeSnapshot;
|
|
78
|
+
readonly inputArtifacts: readonly import("@southwind-ai/shared").AgentWorkflowArtifactRef[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ClientAgentWorkflowExecutionTask =
|
|
82
|
+
ModuleClientAgentWorkflowExecutionTask;
|
|
83
|
+
|
|
84
|
+
export interface ClientAgentWorkflowClaimBinding {
|
|
85
|
+
readonly clientBindingDigest: `sha256:${string}`;
|
|
86
|
+
readonly installationId: string;
|
|
87
|
+
readonly hostSessionDigest: `sha256:${string}`;
|
|
88
|
+
readonly claimTokenHash: `sha256:${string}`;
|
|
89
|
+
readonly claimExpiresAt: number;
|
|
90
|
+
readonly now: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ClientAgentWorkflowTerminalInput {
|
|
94
|
+
readonly claimGeneration: number;
|
|
95
|
+
readonly clientSessionId: string;
|
|
96
|
+
readonly historySequence: number;
|
|
97
|
+
readonly historyHeadDigest: `sha256:${string}`;
|
|
98
|
+
readonly terminalReceiptId: string;
|
|
99
|
+
readonly terminalReceiptDigest: `sha256:${string}`;
|
|
100
|
+
readonly terminalRequestDigest: `sha256:${string}`;
|
|
101
|
+
readonly terminalResult: Readonly<Record<string, unknown>>;
|
|
102
|
+
readonly artifact: ClientAgentWorkflowStoredArtifact;
|
|
103
|
+
readonly now: number;
|
|
104
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { clientAgentWorkflowArtifacts } from "@southwind-ai/database";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
|
+
import { toClientWorkflowArtifact } from "./drizzle-mapper.js";
|
|
5
|
+
|
|
6
|
+
export async function lockClientWorkflowArtifact(
|
|
7
|
+
db: NodePgDatabase,
|
|
8
|
+
workId: string,
|
|
9
|
+
) {
|
|
10
|
+
const [row] = await db
|
|
11
|
+
.select()
|
|
12
|
+
.from(clientAgentWorkflowArtifacts)
|
|
13
|
+
.where(eq(clientAgentWorkflowArtifacts.workId, workId))
|
|
14
|
+
.for("update")
|
|
15
|
+
.limit(1);
|
|
16
|
+
return row ? toClientWorkflowArtifact(row) : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function lockClientWorkflowArtifactBySession(
|
|
20
|
+
db: NodePgDatabase,
|
|
21
|
+
clientSessionId: string,
|
|
22
|
+
) {
|
|
23
|
+
const [row] = await db
|
|
24
|
+
.select()
|
|
25
|
+
.from(clientAgentWorkflowArtifacts)
|
|
26
|
+
.where(eq(clientAgentWorkflowArtifacts.clientSessionId, clientSessionId))
|
|
27
|
+
.for("update")
|
|
28
|
+
.limit(1);
|
|
29
|
+
return row ? toClientWorkflowArtifact(row) : undefined;
|
|
30
|
+
}
|