feat(acp): support expectedFileCountByExtension constraint and dynamic chat timeout
- Add ExpectedFileCounts field to openClawArtifactContract to support per-extension file count validation - Add normalizeOpenClawArtifactExtCountMap and openClawPositiveInt helpers - Propagate expectedFileCountByExtension from contract/metadata/xworkmateArtifactConstraints - Replace hard-coded 2min chat timeout with openClawAgentWaitTimeout for dynamic timeouts - Add test coverage for normalize result and web contract
This commit is contained in:
parent
e6ffdf3177
commit
861816738b
@ -342,12 +342,13 @@ func (o *SessionOrchestrator) startOpenClawGatewayTask(
|
||||
return nil, rpcErr
|
||||
}
|
||||
applyOpenClawPreparedArtifactToChatParams(chatParams, preparedArtifact, sessionKey, turnID, artifactContract)
|
||||
chatSendTimeout := openClawAgentWaitTimeout(params, chatParams)
|
||||
sendStarted := time.Now()
|
||||
sendResult := o.openClawGatewayRequestWithRetry(
|
||||
gatewayProvider,
|
||||
"chat.send",
|
||||
chatParams,
|
||||
2*time.Minute,
|
||||
chatSendTimeout,
|
||||
notifyWithCollection,
|
||||
)
|
||||
logOpenClawGatewayTiming(
|
||||
@ -784,6 +785,7 @@ type openClawArtifactContract struct {
|
||||
RequiresArtifactExport bool
|
||||
ExpectedArtifactDirs []string
|
||||
RequiredArtifactExts []string
|
||||
ExpectedFileCounts map[string]int
|
||||
SourceMessage string
|
||||
}
|
||||
|
||||
@ -806,16 +808,66 @@ func openClawArtifactContractForParams(params map[string]any, chatParams map[str
|
||||
if len(requiredExts) == 0 {
|
||||
requiredExts = inferOpenClawRequiredArtifactExts(lowerMessage)
|
||||
}
|
||||
expectedFileCounts := normalizeOpenClawArtifactExtCountMap(shared.AsMap(contract["expectedFileCountByExtension"]))
|
||||
if len(expectedFileCounts) == 0 {
|
||||
expectedFileCounts = normalizeOpenClawArtifactExtCountMap(shared.AsMap(metadata["expectedFileCountByExtension"]))
|
||||
}
|
||||
if len(expectedFileCounts) == 0 {
|
||||
expectedFileCounts = normalizeOpenClawArtifactExtCountMap(shared.AsMap(shared.AsMap(metadata["xworkmateArtifactConstraints"])["expectedFileCountByExtension"]))
|
||||
}
|
||||
return openClawArtifactContract{
|
||||
TaskLoadClass: taskLoadClass,
|
||||
ComplexLongChain: complex,
|
||||
RequiresArtifactExport: requiresExport,
|
||||
ExpectedArtifactDirs: expectedDirs,
|
||||
RequiredArtifactExts: requiredExts,
|
||||
ExpectedFileCounts: expectedFileCounts,
|
||||
SourceMessage: message,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeOpenClawArtifactExtCountMap(values map[string]any) map[string]int {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := map[string]int{}
|
||||
for key, raw := range values {
|
||||
ext := strings.ToLower(strings.TrimSpace(key))
|
||||
ext = strings.TrimPrefix(ext, ".")
|
||||
if ext == "" || strings.Contains(ext, "/") || strings.Contains(ext, "\\") {
|
||||
continue
|
||||
}
|
||||
count := openClawPositiveInt(raw)
|
||||
if count <= 0 {
|
||||
continue
|
||||
}
|
||||
result[ext] = count
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func openClawPositiveInt(value any) int {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
case float64:
|
||||
return int(v)
|
||||
case float32:
|
||||
return int(v)
|
||||
case string:
|
||||
var parsed int
|
||||
if _, err := fmt.Sscanf(strings.TrimSpace(v), "%d", &parsed); err == nil {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func normalizeOpenClawDirList(values []any) []string {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
@ -1429,6 +1481,13 @@ func (o *SessionOrchestrator) openClawArtifactExport(
|
||||
if len(artifactContract.RequiredArtifactExts) > 0 {
|
||||
exportParams["requiredArtifactExtensions"] = append([]string(nil), artifactContract.RequiredArtifactExts...)
|
||||
}
|
||||
if len(artifactContract.ExpectedFileCounts) > 0 {
|
||||
counts := map[string]int{}
|
||||
for ext, count := range artifactContract.ExpectedFileCounts {
|
||||
counts[ext] = count
|
||||
}
|
||||
exportParams["expectedFileCountByExtension"] = counts
|
||||
}
|
||||
payload := o.openClawArtifactExportRequest(gatewayProvider, exportParams, notify)
|
||||
return payload
|
||||
}
|
||||
@ -1497,6 +1556,9 @@ func mergeOpenClawArtifactPayload(result map[string]any, source map[string]any)
|
||||
if _, ok := source["missingRequiredExtensions"]; ok {
|
||||
result["missingRequiredExtensions"] = appendStringList(result["missingRequiredExtensions"], source["missingRequiredExtensions"])
|
||||
}
|
||||
if value, ok := source["missingRequiredFileCounts"]; ok {
|
||||
result["missingRequiredFileCounts"] = value
|
||||
}
|
||||
}
|
||||
|
||||
func appendStringList(existing any, incoming any) []any {
|
||||
|
||||
@ -263,6 +263,9 @@ func TestTaskGetArtifactExportReceivesRequiredArtifactExtensions(t *testing.T) {
|
||||
"gatewayProviderId": shared.StringArg(start, "resolvedGatewayProviderId", ""),
|
||||
"requiresArtifactExport": true,
|
||||
"requiredArtifactExtensions": []any{"pdf"},
|
||||
"expectedFileCountByExtension": map[string]any{
|
||||
"pdf": 1,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if rpcErr != nil {
|
||||
@ -275,4 +278,7 @@ func TestTaskGetArtifactExportReceivesRequiredArtifactExtensions(t *testing.T) {
|
||||
if got := shared.ListArg(exportParams, "requiredArtifactExtensions"); len(got) != 1 || got[0] != "pdf" {
|
||||
t.Fatalf("expected requiredArtifactExtensions to reach export, got %#v", exportParams)
|
||||
}
|
||||
if got := shared.AsMap(exportParams["expectedFileCountByExtension"]); openClawPositiveInt(got["pdf"]) != 1 {
|
||||
t.Fatalf("expected expectedFileCountByExtension to reach export, got %#v", exportParams)
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,6 +245,9 @@ func (s *Server) mergeOpenClawTaskGetArtifactExport(payload map[string]any, para
|
||||
if requiredExts := openClawTaskGetRequiredArtifactExtensions(params, payload); len(requiredExts) > 0 {
|
||||
exportParams["requiredArtifactExtensions"] = append([]string(nil), requiredExts...)
|
||||
}
|
||||
if expectedCounts := openClawTaskGetExpectedFileCounts(params, payload); len(expectedCounts) > 0 {
|
||||
exportParams["expectedFileCountByExtension"] = expectedCounts
|
||||
}
|
||||
exportPayload := s.orchestrator.openClawArtifactExportRequest(gatewayProvider, exportParams, notify)
|
||||
if openClawArtifactExportPayloadAuthoritative(exportPayload) {
|
||||
replaceOpenClawArtifactPayload(payload, exportPayload)
|
||||
@ -491,6 +494,20 @@ func openClawTaskGetRequiredArtifactExtensions(params map[string]any, payload ma
|
||||
return normalizeOpenClawArtifactExtList(openClawTaskGetMergedList(params, payload, "requiredArtifactExtensions"))
|
||||
}
|
||||
|
||||
func openClawTaskGetExpectedFileCounts(params map[string]any, payload map[string]any) map[string]int {
|
||||
result := normalizeOpenClawArtifactExtCountMap(shared.AsMap(payload["expectedFileCountByExtension"]))
|
||||
for ext, count := range normalizeOpenClawArtifactExtCountMap(shared.AsMap(params["expectedFileCountByExtension"])) {
|
||||
if result == nil {
|
||||
result = map[string]int{}
|
||||
}
|
||||
result[ext] = count
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func openClawTaskGetMergedList(params map[string]any, payload map[string]any, key string) []any {
|
||||
seen := map[string]bool{}
|
||||
result := []any{}
|
||||
|
||||
@ -394,11 +394,11 @@ func TestHTTPHandlerGatewayOpenClawHandlesFiveConcurrentE2ECases(t *testing.T) {
|
||||
defer httpServer.Close()
|
||||
|
||||
prompts := []string{
|
||||
"采集最新AI资讯,保存在md文件",
|
||||
"围绕\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 右侧是当下 \n测试制作视频,附件带有图片",
|
||||
"从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 \n制作 使用codex 制作连续制作 7张的一些列图片",
|
||||
"参考附件模版制作 ,围绕\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 \n连续制作 7张的一些列图片",
|
||||
"拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 -> 输出 PDF\n\n右侧 artifact栏 显示的陈旧文件 make artifact",
|
||||
"围绕\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 右侧是当下 \n测试制作视频",
|
||||
"围绕\n\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 \n\n拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 -> 制作视频",
|
||||
"围绕\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进\n输出Markdown格式文件, 微信公众号短图文 400-600字 插入关键词的软文\n输出Markdown格式文件, 小红书风格 600-800字 插入钩子话题的软文\n输出Markdown格式文件, X文案串 小于144字的英语 鲜明的观点\n输出Markdown格式文件, 微信公众号文章 800-1200字左右\n输出Markdown格式文件, 头条号长文 800-1200字左右",
|
||||
"围绕\n\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 \n\n拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 -> 输出 PDF",
|
||||
}
|
||||
type result struct {
|
||||
body string
|
||||
|
||||
Loading…
Reference in New Issue
Block a user