fix(acp): S1 — default expectedArtifactDirs so plugin root-fallback collects artifacts

Live verification (docs/cases/06 §7 S1) showed the session mapping recorded
expectedArtifactDirs:[] for an md-producing task. openclaw-multi-session-plugins
only scans the workspace-root deliverable dirs (reports/, artifacts/, ...) when
expectedArtifactDirs is non-empty; empty → the root fallback is inert, so an agent
that writes news.md to the workspace root (the common case) yields "no files".

openClawArtifactContractForParams now defaults expectedArtifactDirs to
reports//artifacts//exports/ when the task expects artifacts (requiresExport or
inferred requiredExts) but declared no dirs, and marks requiresExport so the export
path runs. Pure-chat turns (no artifact intent) are unaffected.

Test: orchestrator_s1_artifact_dirs_test.go (md task gets dirs+export; chat gets neither).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Haitao Pan 2026-06-27 06:31:34 +08:00
parent 2333c3e5fd
commit 3c7de420d2
2 changed files with 33 additions and 0 deletions

View File

@ -850,6 +850,12 @@ type openClawArtifactContract struct {
SourceMessage string
}
// defaultOpenClawExpectedArtifactDirs 是 agent 最常用的产物落盘目录。当任务期望产物却没有
// 显式声明目录时,用作 openclaw-multi-session-plugins 的 workspace 根兜底扫描范围S1
func defaultOpenClawExpectedArtifactDirs() []string {
return []string{"reports/", "artifacts/", "exports/"}
}
func openClawArtifactContractForParams(params map[string]any, chatParams map[string]any) openClawArtifactContract {
metadata := shared.AsMap(params["metadata"])
taskLoadClass := strings.TrimSpace(shared.StringArg(metadata, "taskLoadClass", ""))
@ -869,6 +875,14 @@ func openClawArtifactContractForParams(params map[string]any, chatParams map[str
if len(requiredExts) == 0 {
requiredExts = inferOpenClawRequiredArtifactExts(lowerMessage)
}
// S1docs/cases/06 §7任务期望产物需导出 或 已推断出 requiredExts却没有显式声明
// expectedArtifactDirs 时补一组缺省目录。openclaw-multi-session-plugins 在 task scope
// 目录为空时会回扫 workspace 根的 expectedArtifactDirs该列表为空则兜底形同虚设
// agent 写到 workspace 根reports//artifacts//exports/)的产物就再也收不回(表现「暂无文件」)。
if len(expectedDirs) == 0 && (requiresExport || len(requiredExts) > 0) {
expectedDirs = defaultOpenClawExpectedArtifactDirs()
requiresExport = true
}
expectedFileCounts := normalizeOpenClawArtifactExtCountMap(shared.AsMap(contract["expectedFileCountByExtension"]))
if len(expectedFileCounts) == 0 {
expectedFileCounts = normalizeOpenClawArtifactExtCountMap(shared.AsMap(metadata["expectedFileCountByExtension"]))

View File

@ -0,0 +1,19 @@
package acp
import "testing"
func TestS1DefaultExpectedArtifactDirs(t *testing.T) {
// 任务消息要求产出 md → 推断 requiredExts但未声明 expectedArtifactDirs → 应补缺省目录
c := openClawArtifactContractForParams(
map[string]any{}, map[string]any{"message": "采集最新AI资讯保存在md文件"})
if len(c.ExpectedArtifactDirs) == 0 {
t.Fatalf("expected default artifact dirs for an md-producing task, got none")
}
if !c.RequiresArtifactExport {
t.Fatalf("expected RequiresArtifactExport=true when default dirs applied")
}
// 纯聊天(无产物意图)→ 不应补目录、不应强制导出
c2 := openClawArtifactContractForParams(
map[string]any{}, map[string]any{"message": "你好,介绍一下你自己"})
if len(c2.ExpectedArtifactDirs) != 0 || c2.RequiresArtifactExport {
t.Fatalf("pure chat should not get default dirs / forced export, got dirs=%v export=%v", c2.ExpectedArtifactDirs, c2.RequiresArtifactExport)
}
}