refactor(acp): replace artifact fallback chain with snapshot+export and stable session mapping
- Introduce ThreadSessionMapper to derive stable OpenClaw session keys from threadId/sessionId, avoiding leaked draft session identifiers - Replace the artifact scope cascading fallback (output-token heuristics, draft variant retries) with a single collect-and-snapshot call followed by export, per anti-fallback rules - Enforce artifact contract by failing runs that report success but miss required final artifact extensions - Update orchestrator and tests to the new methods sequence (collect-and-snapshot before export) - Relax AGENTS.md rule to allow updating tests when the protocol contract itself changes
This commit is contained in:
parent
1f43a989a0
commit
fc965b3ec4
@ -44,4 +44,4 @@ Notes:
|
|||||||
- Run `go vet` and ensure zero warnings before committing.
|
- Run `go vet` and ensure zero warnings before committing.
|
||||||
- Run `go build ./...` and verify compilation succeeds after every refactor.
|
- Run `go build ./...` and verify compilation succeeds after every refactor.
|
||||||
- After removing a source file, verify that no remaining file imports it or references its exported symbols.
|
- After removing a source file, verify that no remaining file imports it or references its exported symbols.
|
||||||
- Do not modify `*_test.go` files. If a refactor breaks a test, adjust the production code to keep the test passing.
|
- Do not modify `*_test.go` files just to hide a production regression. When a requested behavior or protocol contract changes, update the nearest tests first or in the same change, and keep the assertions tied to the new contract.
|
||||||
|
|||||||
@ -475,13 +475,21 @@ func (o *SessionOrchestrator) completeOpenClawTask(
|
|||||||
mergeOpenClawArtifactPayload(result, collector.artifactPayload())
|
mergeOpenClawArtifactPayload(result, collector.artifactPayload())
|
||||||
}
|
}
|
||||||
applyOpenClawPreparedArtifactToResult(result, record.PreparedArtifact)
|
applyOpenClawPreparedArtifactToResult(result, record.PreparedArtifact)
|
||||||
|
snapshotPayload := o.openClawArtifactCollectAndSnapshot(
|
||||||
|
record.GatewayProviderID,
|
||||||
|
record.ChatParams,
|
||||||
|
record.RunID,
|
||||||
|
record.ArtifactSinceUnixMs,
|
||||||
|
record.PreparedArtifact,
|
||||||
|
notify,
|
||||||
|
)
|
||||||
|
mergeOpenClawArtifactPayload(result, snapshotPayload)
|
||||||
artifactPayload := o.openClawArtifactExport(
|
artifactPayload := o.openClawArtifactExport(
|
||||||
record.GatewayProviderID,
|
record.GatewayProviderID,
|
||||||
record.ChatParams,
|
record.ChatParams,
|
||||||
record.RunID,
|
record.RunID,
|
||||||
record.ArtifactSinceUnixMs,
|
record.ArtifactSinceUnixMs,
|
||||||
record.PreparedArtifact,
|
record.PreparedArtifact,
|
||||||
output,
|
|
||||||
notify,
|
notify,
|
||||||
)
|
)
|
||||||
mergeOpenClawArtifactPayload(result, artifactPayload)
|
mergeOpenClawArtifactPayload(result, artifactPayload)
|
||||||
|
|||||||
36
internal/acp/openclaw_thread_session_mapper.go
Normal file
36
internal/acp/openclaw_thread_session_mapper.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package acp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ThreadSessionMapper struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
sessions map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewThreadSessionMapper() *ThreadSessionMapper {
|
||||||
|
return &ThreadSessionMapper{sessions: make(map[string]string)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ThreadSessionMapper) OpenClawSessionID(threadID string, sessionID string) string {
|
||||||
|
key := strings.TrimSpace(threadID)
|
||||||
|
if key == "" {
|
||||||
|
key = strings.TrimSpace(sessionID)
|
||||||
|
}
|
||||||
|
if key == "" {
|
||||||
|
key = "main"
|
||||||
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if existing := strings.TrimSpace(m.sessions[key]); existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256([]byte(key))
|
||||||
|
session := "xwm-" + hex.EncodeToString(sum[:])[:24]
|
||||||
|
m.sessions[key] = session
|
||||||
|
return session
|
||||||
|
}
|
||||||
@ -324,9 +324,9 @@ func (o *SessionOrchestrator) startOpenClawGatewayTask(
|
|||||||
notify(update)
|
notify(update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sessionKey := openClawSessionKey(params, turnID)
|
sessionKey := o.openClawSessionKey(params, turnID)
|
||||||
params = withOpenClawWritableWorkspace(params, sessionKey)
|
params = withOpenClawWritableWorkspace(params, sessionKey)
|
||||||
chatParams, rpcErr := openClawChatSendParams(params, turnID)
|
chatParams, rpcErr := openClawChatSendParamsWithSessionKey(params, turnID, sessionKey)
|
||||||
if rpcErr != nil {
|
if rpcErr != nil {
|
||||||
return nil, rpcErr
|
return nil, rpcErr
|
||||||
}
|
}
|
||||||
@ -863,12 +863,19 @@ func normalizeOpenClawArtifactExtension(value string) string {
|
|||||||
func openClawChatSendParams(
|
func openClawChatSendParams(
|
||||||
params map[string]any,
|
params map[string]any,
|
||||||
turnID string,
|
turnID string,
|
||||||
|
) (map[string]any, *shared.RPCError) {
|
||||||
|
return openClawChatSendParamsWithSessionKey(params, turnID, fallbackOpenClawSessionKey(params, turnID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func openClawChatSendParamsWithSessionKey(
|
||||||
|
params map[string]any,
|
||||||
|
turnID string,
|
||||||
|
sessionKey string,
|
||||||
) (map[string]any, *shared.RPCError) {
|
) (map[string]any, *shared.RPCError) {
|
||||||
message := openClawCurrentTurnMessage(params)
|
message := openClawCurrentTurnMessage(params)
|
||||||
if message == "" {
|
if message == "" {
|
||||||
return nil, &shared.RPCError{Code: -32602, Message: "OPENCLAW_TASK_PROMPT_REQUIRED"}
|
return nil, &shared.RPCError{Code: -32602, Message: "OPENCLAW_TASK_PROMPT_REQUIRED"}
|
||||||
}
|
}
|
||||||
sessionKey := openClawSessionKey(params, turnID)
|
|
||||||
chatParams := map[string]any{
|
chatParams := map[string]any{
|
||||||
"sessionKey": sessionKey,
|
"sessionKey": sessionKey,
|
||||||
"message": message,
|
"message": message,
|
||||||
@ -1278,7 +1285,16 @@ func compactOpenClawTexts(texts []string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func openClawSessionKey(params map[string]any, turnID string) string {
|
func (o *SessionOrchestrator) openClawSessionKey(params map[string]any, turnID string) string {
|
||||||
|
threadID := strings.TrimSpace(shared.StringArg(params, "threadId", ""))
|
||||||
|
sessionID := strings.TrimSpace(shared.StringArg(params, "sessionId", ""))
|
||||||
|
if o != nil && o.server != nil && o.server.openClawSessions != nil {
|
||||||
|
return o.server.openClawSessions.OpenClawSessionID(threadID, sessionID)
|
||||||
|
}
|
||||||
|
return fallbackOpenClawSessionKey(params, turnID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackOpenClawSessionKey(params map[string]any, turnID string) string {
|
||||||
for _, key := range []string{"threadId", "sessionId"} {
|
for _, key := range []string{"threadId", "sessionId"} {
|
||||||
if value := strings.TrimSpace(shared.StringArg(params, key, "")); value != "" {
|
if value := strings.TrimSpace(shared.StringArg(params, key, "")); value != "" {
|
||||||
return value
|
return value
|
||||||
@ -1296,7 +1312,6 @@ func (o *SessionOrchestrator) openClawArtifactExport(
|
|||||||
runID string,
|
runID string,
|
||||||
sinceUnixMs int64,
|
sinceUnixMs int64,
|
||||||
preparedArtifact *openClawPreparedArtifactScope,
|
preparedArtifact *openClawPreparedArtifactScope,
|
||||||
outputText string,
|
|
||||||
notify func(map[string]any),
|
notify func(map[string]any),
|
||||||
) map[string]any {
|
) map[string]any {
|
||||||
sessionKey := strings.TrimSpace(shared.StringArg(chatParams, "sessionKey", ""))
|
sessionKey := strings.TrimSpace(shared.StringArg(chatParams, "sessionKey", ""))
|
||||||
@ -1315,30 +1330,49 @@ func (o *SessionOrchestrator) openClawArtifactExport(
|
|||||||
exportParams["artifactScope"] = strings.TrimSpace(preparedArtifact.ArtifactScope)
|
exportParams["artifactScope"] = strings.TrimSpace(preparedArtifact.ArtifactScope)
|
||||||
}
|
}
|
||||||
payload := o.openClawArtifactExportRequest(gatewayProvider, exportParams, notify)
|
payload := o.openClawArtifactExportRequest(gatewayProvider, exportParams, notify)
|
||||||
if openClawArtifactPayloadCount(payload) > 0 {
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
fallbackScope := openClawArtifactScopeFromOutput(outputText, runID)
|
|
||||||
if fallbackScope != "" && fallbackScope != strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", "")) {
|
|
||||||
fallbackParams := cloneMap(exportParams)
|
|
||||||
applyOpenClawArtifactScopeFallbackParams(fallbackParams, fallbackScope)
|
|
||||||
fallbackPayload := o.openClawArtifactExportRequest(gatewayProvider, fallbackParams, notify)
|
|
||||||
if openClawArtifactPayloadCount(fallbackPayload) > 0 {
|
|
||||||
return fallbackPayload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, fallbackScope := range openClawArtifactScopeVariants(strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", ""))) {
|
|
||||||
fallbackParams := cloneMap(exportParams)
|
|
||||||
applyOpenClawArtifactScopeFallbackParams(fallbackParams, fallbackScope)
|
|
||||||
fallbackPayload := o.openClawArtifactExportRequest(gatewayProvider, fallbackParams, notify)
|
|
||||||
if openClawArtifactPayloadCount(fallbackPayload) > 0 {
|
|
||||||
return fallbackPayload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *SessionOrchestrator) openClawArtifactCollectAndSnapshot(
|
||||||
|
gatewayProvider string,
|
||||||
|
chatParams map[string]any,
|
||||||
|
runID string,
|
||||||
|
sinceUnixMs int64,
|
||||||
|
preparedArtifact *openClawPreparedArtifactScope,
|
||||||
|
notify func(map[string]any),
|
||||||
|
) map[string]any {
|
||||||
|
sessionKey := strings.TrimSpace(shared.StringArg(chatParams, "sessionKey", ""))
|
||||||
|
if sessionKey == "" || strings.TrimSpace(runID) == "" || preparedArtifact == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
snapshotParams := map[string]any{
|
||||||
|
"sessionKey": sessionKey,
|
||||||
|
"runId": strings.TrimSpace(runID),
|
||||||
|
"sinceUnixMs": sinceUnixMs,
|
||||||
|
"maxFiles": 64,
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(preparedArtifact.ArtifactScope) != "" {
|
||||||
|
snapshotParams["artifactScope"] = strings.TrimSpace(preparedArtifact.ArtifactScope)
|
||||||
|
}
|
||||||
|
snapshotResult := o.openClawGatewayRequestWithRetry(
|
||||||
|
gatewayProvider,
|
||||||
|
"xworkmate.artifacts.collect-and-snapshot",
|
||||||
|
snapshotParams,
|
||||||
|
30*time.Second,
|
||||||
|
notify,
|
||||||
|
)
|
||||||
|
if snapshotResult.OK {
|
||||||
|
return shared.AsMap(snapshotResult.Payload)
|
||||||
|
}
|
||||||
|
message := strings.TrimSpace(shared.StringArg(snapshotResult.Error, "message", ""))
|
||||||
|
if message == "" {
|
||||||
|
message = "openclaw artifact snapshot unavailable"
|
||||||
|
}
|
||||||
|
return map[string]any{
|
||||||
|
"artifactWarnings": []any{message},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (o *SessionOrchestrator) openClawArtifactExportRequest(
|
func (o *SessionOrchestrator) openClawArtifactExportRequest(
|
||||||
gatewayProvider string,
|
gatewayProvider string,
|
||||||
exportParams map[string]any,
|
exportParams map[string]any,
|
||||||
@ -1363,72 +1397,6 @@ func (o *SessionOrchestrator) openClawArtifactExportRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openClawArtifactScopeFromOutput(outputText string, runID string) string {
|
|
||||||
runID = strings.TrimSpace(runID)
|
|
||||||
if strings.TrimSpace(outputText) == "" || runID == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
for _, token := range strings.Fields(outputText) {
|
|
||||||
scope := openClawArtifactScopeFromOutputToken(token, runID)
|
|
||||||
if scope != "" {
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func openClawArtifactScopeFromOutputToken(token string, runID string) string {
|
|
||||||
token = strings.Trim(token, " \t\r\n`'\".,;:()[]{}<>")
|
|
||||||
token = strings.ReplaceAll(token, "\\", "/")
|
|
||||||
index := strings.Index(token, "/tasks/")
|
|
||||||
if index < 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
segments := strings.Split(token[index+1:], "/")
|
|
||||||
if len(segments) < 4 || segments[0] != "tasks" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
sessionSegment := strings.TrimSpace(segments[1])
|
|
||||||
runSegment := strings.TrimSpace(segments[2])
|
|
||||||
relativeFile := safeOpenClawArtifactDownloadRelativePath(strings.Join(segments[3:], "/"))
|
|
||||||
if sessionSegment == "" || runSegment != runID || relativeFile == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return "tasks/" + sessionSegment + "/" + runSegment
|
|
||||||
}
|
|
||||||
|
|
||||||
func openClawArtifactScopeVariants(scope string) []string {
|
|
||||||
scope = strings.TrimSpace(scope)
|
|
||||||
parts := strings.Split(scope, "/")
|
|
||||||
if len(parts) != 3 || parts[0] != "tasks" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sessionSegment := strings.TrimSpace(parts[1])
|
|
||||||
runSegment := strings.TrimSpace(parts[2])
|
|
||||||
if sessionSegment == "" || runSegment == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var variants []string
|
|
||||||
if strings.HasPrefix(sessionSegment, "draft-") {
|
|
||||||
variants = append(variants, "tasks/"+strings.Replace(sessionSegment, "draft-", "draft_", 1)+"/"+runSegment)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(sessionSegment, "draft_") {
|
|
||||||
variants = append(variants, "tasks/"+strings.Replace(sessionSegment, "draft_", "draft-", 1)+"/"+runSegment)
|
|
||||||
}
|
|
||||||
return variants
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyOpenClawArtifactScopeFallbackParams(params map[string]any, scope string) {
|
|
||||||
if params == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scope = strings.TrimSpace(scope)
|
|
||||||
params["artifactScope"] = scope
|
|
||||||
if sessionKey := openClawSessionKeyFromArtifactScope(scope); sessionKey != "" {
|
|
||||||
params["sessionKey"] = sessionKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func openClawSessionKeyFromArtifactScope(scope string) string {
|
func openClawSessionKeyFromArtifactScope(scope string) string {
|
||||||
parts := strings.Split(strings.TrimSpace(scope), "/")
|
parts := strings.Split(strings.TrimSpace(scope), "/")
|
||||||
if len(parts) != 3 || parts[0] != "tasks" {
|
if len(parts) != 3 || parts[0] != "tasks" {
|
||||||
@ -1485,10 +1453,40 @@ func applyOpenClawArtifactContractResult(result map[string]any, contract openCla
|
|||||||
if len(contract.ExpectedArtifactExtensions) > 0 {
|
if len(contract.ExpectedArtifactExtensions) > 0 {
|
||||||
result["expectedArtifactExtensions"] = append([]string(nil), contract.ExpectedArtifactExtensions...)
|
result["expectedArtifactExtensions"] = append([]string(nil), contract.ExpectedArtifactExtensions...)
|
||||||
}
|
}
|
||||||
|
if !parseBool(result["success"]) || len(contract.ExpectedArtifactExtensions) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remoteWorkingDirectory := strings.TrimSpace(shared.StringArg(result, "remoteWorkingDirectory", ""))
|
||||||
|
artifacts := extractArtifactPayloads(result, remoteWorkingDirectory)
|
||||||
|
found := map[string]bool{}
|
||||||
|
for _, artifact := range artifacts {
|
||||||
|
if extension := openClawArtifactExtension(artifact); extension != "" {
|
||||||
|
found[extension] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
missing := make([]string, 0, len(contract.ExpectedArtifactExtensions))
|
||||||
|
for _, extension := range contract.ExpectedArtifactExtensions {
|
||||||
|
if !found[extension] {
|
||||||
|
missing = append(missing, extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message := openClawRequiredArtifactMissingText
|
||||||
|
if len(artifacts) > 0 {
|
||||||
|
message = "openclaw returned partial artifacts without required final deliverables"
|
||||||
|
}
|
||||||
|
result["success"] = false
|
||||||
|
result["status"] = string(TaskStateFailed)
|
||||||
|
result["code"] = "OPENCLAW_REQUIRED_ARTIFACT_MISSING"
|
||||||
|
result["error"] = message
|
||||||
|
result["message"] = message
|
||||||
|
result["output"] = message
|
||||||
|
result["summary"] = message
|
||||||
|
result["missingArtifactExtensions"] = missing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func openClawArtifactExtension(artifact map[string]any) string {
|
func openClawArtifactExtension(artifact map[string]any) string {
|
||||||
for _, key := range []string{"relativePath", "path", "label", "name"} {
|
for _, key := range []string{"relativePath", "path", "label", "name"} {
|
||||||
value := strings.TrimSpace(shared.StringArg(artifact, key, ""))
|
value := strings.TrimSpace(shared.StringArg(artifact, key, ""))
|
||||||
@ -1958,7 +1956,7 @@ func (o *SessionOrchestrator) completeOpenClawScopedArtifactExport(
|
|||||||
if preparedArtifact == nil {
|
if preparedArtifact == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sessionKey := openClawSessionKey(params, turnID)
|
sessionKey := o.openClawSessionKey(params, turnID)
|
||||||
runID := strings.TrimSpace(shared.StringArg(result, "runId", turnID))
|
runID := strings.TrimSpace(shared.StringArg(result, "runId", turnID))
|
||||||
chatParams := map[string]any{"sessionKey": sessionKey}
|
chatParams := map[string]any{"sessionKey": sessionKey}
|
||||||
mergeOpenClawArtifactPayload(result, o.openClawArtifactExport(
|
mergeOpenClawArtifactPayload(result, o.openClawArtifactExport(
|
||||||
@ -1967,7 +1965,6 @@ func (o *SessionOrchestrator) completeOpenClawScopedArtifactExport(
|
|||||||
runID,
|
runID,
|
||||||
0,
|
0,
|
||||||
preparedArtifact,
|
preparedArtifact,
|
||||||
firstNonEmptyString(result, "output", "message", "summary", "assistantText", "text"),
|
|
||||||
nil,
|
nil,
|
||||||
))
|
))
|
||||||
o.server.decorateOpenClawArtifactDownloadURLs(result, sessionKey, runID)
|
o.server.decorateOpenClawArtifactDownloadURLs(result, sessionKey, runID)
|
||||||
|
|||||||
@ -534,11 +534,15 @@ func TestExecuteSessionTaskGatewayAutoConnectsLocalOpenClaw(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
receipt := strings.TrimSpace(shared.StringArg(chatParams, "systemProvenanceReceipt", ""))
|
receipt := strings.TrimSpace(shared.StringArg(chatParams, "systemProvenanceReceipt", ""))
|
||||||
|
openClawSessionKey := shared.StringArg(chatParams, "sessionKey", "")
|
||||||
|
if openClawSessionKey == "" || openClawSessionKey == "thread-openclaw" {
|
||||||
|
t.Fatalf("expected mapped OpenClaw sessionKey, got %#v", chatParams)
|
||||||
|
}
|
||||||
for _, expected := range []string{
|
for _, expected := range []string{
|
||||||
"artifactDirectory: /remote/openclaw/workspace/tasks/thread-openclaw/" + shared.StringArg(chatParams, "idempotencyKey", ""),
|
"artifactDirectory: /remote/openclaw/workspace/tasks/" + openClawSessionKey + "/" + shared.StringArg(chatParams, "idempotencyKey", ""),
|
||||||
"artifactScope: tasks/thread-openclaw/" + shared.StringArg(chatParams, "idempotencyKey", ""),
|
"artifactScope: tasks/" + openClawSessionKey + "/" + shared.StringArg(chatParams, "idempotencyKey", ""),
|
||||||
"export XWORKMATE_TASK_ARTIFACT_DIR='/remote/openclaw/workspace/tasks/thread-openclaw/" + shared.StringArg(chatParams, "idempotencyKey", "") + "'",
|
"export XWORKMATE_TASK_ARTIFACT_DIR='/remote/openclaw/workspace/tasks/" + openClawSessionKey + "/" + shared.StringArg(chatParams, "idempotencyKey", "") + "'",
|
||||||
"cd '/remote/openclaw/workspace/tasks/thread-openclaw/" + shared.StringArg(chatParams, "idempotencyKey", "") + "'",
|
"cd '/remote/openclaw/workspace/tasks/" + openClawSessionKey + "/" + shared.StringArg(chatParams, "idempotencyKey", "") + "'",
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(receipt, expected) {
|
if !strings.Contains(receipt, expected) {
|
||||||
t.Fatalf("expected chat.send systemProvenanceReceipt to include %q, got %q", expected, receipt)
|
t.Fatalf("expected chat.send systemProvenanceReceipt to include %q, got %q", expected, receipt)
|
||||||
@ -558,7 +562,7 @@ func TestExecuteSessionTaskGatewayAutoConnectsLocalOpenClaw(t *testing.T) {
|
|||||||
if gateway.ArtifactExportCount() != 1 {
|
if gateway.ArtifactExportCount() != 1 {
|
||||||
t.Fatalf("expected one OpenClaw artifact export sync after run, got %d", gateway.ArtifactExportCount())
|
t.Fatalf("expected one OpenClaw artifact export sync after run, got %d", gateway.ArtifactExportCount())
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||||
}
|
}
|
||||||
client := gateway.LastConnectClient()
|
client := gateway.LastConnectClient()
|
||||||
@ -848,7 +852,6 @@ func TestExecuteSessionTaskGatewayComplexArtifactContractAcceptsRequiredFinalArt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestExecuteSessionTaskGatewayFailsArtifactContractAfterWaitFailure(t *testing.T) {
|
func TestExecuteSessionTaskGatewayFailsArtifactContractAfterWaitFailure(t *testing.T) {
|
||||||
gateway := newAcpFakeOpenClawGateway(t)
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
defer gateway.Close()
|
defer gateway.Close()
|
||||||
@ -944,8 +947,6 @@ func TestExecuteSessionTaskGatewayKeepsRunningOnNonTerminalWaitPayload(t *testin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func TestExecuteSessionTaskGatewayAgentFailedBeforeReplyReturnsFailureCode(t *testing.T) {
|
func TestExecuteSessionTaskGatewayAgentFailedBeforeReplyReturnsFailureCode(t *testing.T) {
|
||||||
gateway := newAcpFakeOpenClawGateway(t)
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
defer gateway.Close()
|
defer gateway.Close()
|
||||||
@ -1026,7 +1027,7 @@ func TestExecuteSessionMessageGatewayUsesOpenClawChatSend(t *testing.T) {
|
|||||||
if gateway.ArtifactExportCount() != 1 {
|
if gateway.ArtifactExportCount() != 1 {
|
||||||
t.Fatalf("expected one OpenClaw artifact export sync after message run, got %d", gateway.ArtifactExportCount())
|
t.Fatalf("expected one OpenClaw artifact export sync after message run, got %d", gateway.ArtifactExportCount())
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1665,13 +1666,13 @@ func TestExecuteSessionTaskGatewayExportsOpenClawArtifacts(t *testing.T) {
|
|||||||
if got := parsedDownloadURL.Path; got != openClawArtifactDownloadPath {
|
if got := parsedDownloadURL.Path; got != openClawArtifactDownloadPath {
|
||||||
t.Fatalf("expected bridge artifact download path, got %q from %q", got, downloadURL)
|
t.Fatalf("expected bridge artifact download path, got %q from %q", got, downloadURL)
|
||||||
}
|
}
|
||||||
if got := parsedDownloadURL.Query().Get("sessionKey"); got != "thread-openclaw-artifact" {
|
if got := parsedDownloadURL.Query().Get("sessionKey"); got != shared.StringArg(response, "sessionKey", "") {
|
||||||
t.Fatalf("expected thread sessionKey in downloadUrl, got %q", got)
|
t.Fatalf("expected mapped sessionKey in downloadUrl, got %q", got)
|
||||||
}
|
}
|
||||||
if got := parsedDownloadURL.Query().Get("relativePath"); got != "reports/final.md" {
|
if got := parsedDownloadURL.Query().Get("relativePath"); got != "reports/final.md" {
|
||||||
t.Fatalf("expected artifact relativePath in downloadUrl, got %q", got)
|
t.Fatalf("expected artifact relativePath in downloadUrl, got %q", got)
|
||||||
}
|
}
|
||||||
if artifactScope := parsedDownloadURL.Query().Get("artifactScope"); artifactScope != "tasks/thread-openclaw-artifact/"+response["runId"].(string) {
|
if artifactScope := parsedDownloadURL.Query().Get("artifactScope"); artifactScope != "tasks/"+shared.StringArg(response, "sessionKey", "")+"/"+response["runId"].(string) {
|
||||||
t.Fatalf("expected prepared artifact scope in downloadUrl, got %q", artifactScope)
|
t.Fatalf("expected prepared artifact scope in downloadUrl, got %q", artifactScope)
|
||||||
}
|
}
|
||||||
if parsedDownloadURL.Query().Get("sig") == "" {
|
if parsedDownloadURL.Query().Get("sig") == "" {
|
||||||
@ -1684,7 +1685,7 @@ func TestExecuteSessionTaskGatewayExportsOpenClawArtifacts(t *testing.T) {
|
|||||||
if got := shared.BoolArg(shared.StringArg(exportParams, "includeContent", ""), true); got {
|
if got := shared.BoolArg(shared.StringArg(exportParams, "includeContent", ""), true); got {
|
||||||
t.Fatalf("expected OpenClaw artifact export to omit content, got %#v", exportParams)
|
t.Fatalf("expected OpenClaw artifact export to omit content, got %#v", exportParams)
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1735,7 +1736,7 @@ func TestExecuteSessionTaskGatewayDoesNotTreatPromptTextAsArtifactContract(t *te
|
|||||||
if got := shared.BoolArg(shared.StringArg(exportParams, "includeContent", ""), true); got {
|
if got := shared.BoolArg(shared.StringArg(exportParams, "includeContent", ""), true); got {
|
||||||
t.Fatalf("expected latest workspace export to omit content, got %#v", exportParams)
|
t.Fatalf("expected latest workspace export to omit content, got %#v", exportParams)
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1778,7 +1779,7 @@ func TestExecuteSessionTaskGatewayExportsWithActualOpenClawRunID(t *testing.T) {
|
|||||||
if got := strings.TrimSpace(shared.StringArg(exportParams, "runId", "")); got != "openclaw-run-actual" {
|
if got := strings.TrimSpace(shared.StringArg(exportParams, "runId", "")); got != "openclaw-run-actual" {
|
||||||
t.Fatalf("expected artifact export to use actual OpenClaw runId, got %#v", exportParams)
|
t.Fatalf("expected artifact export to use actual OpenClaw runId, got %#v", exportParams)
|
||||||
}
|
}
|
||||||
if got := strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", "")); got != "tasks/thread-openclaw-actual-run/openclaw-run-actual" {
|
if got := strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", "")); got != "tasks/"+shared.StringArg(response, "sessionKey", "")+"/openclaw-run-actual" {
|
||||||
t.Fatalf("expected artifact export to use actual OpenClaw run scope, got %#v", exportParams)
|
t.Fatalf("expected artifact export to use actual OpenClaw run scope, got %#v", exportParams)
|
||||||
}
|
}
|
||||||
artifacts, ok := response["artifacts"].([]map[string]any)
|
artifacts, ok := response["artifacts"].([]map[string]any)
|
||||||
@ -1793,15 +1794,15 @@ func TestExecuteSessionTaskGatewayExportsWithActualOpenClawRunID(t *testing.T) {
|
|||||||
if got := parsedDownloadURL.Query().Get("runId"); got != "openclaw-run-actual" {
|
if got := parsedDownloadURL.Query().Get("runId"); got != "openclaw-run-actual" {
|
||||||
t.Fatalf("expected download URL to use actual OpenClaw runId, got %q from %q", got, downloadURL)
|
t.Fatalf("expected download URL to use actual OpenClaw runId, got %q from %q", got, downloadURL)
|
||||||
}
|
}
|
||||||
if got := parsedDownloadURL.Query().Get("artifactScope"); got != "tasks/thread-openclaw-actual-run/openclaw-run-actual" {
|
if got := parsedDownloadURL.Query().Get("artifactScope"); got != "tasks/"+shared.StringArg(response, "sessionKey", "")+"/openclaw-run-actual" {
|
||||||
t.Fatalf("expected download URL to use actual OpenClaw artifact scope, got %q", got)
|
t.Fatalf("expected download URL to use actual OpenClaw artifact scope, got %q", got)
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "xworkmate.artifacts.prepare", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "xworkmate.artifacts.prepare", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected bridge to reprepare actual OpenClaw run before wait/export, got %#v", got)
|
t.Fatalf("expected bridge to reprepare actual OpenClaw run before wait/export, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteSessionTaskGatewayExportsArtifactScopeDeclaredInOutput(t *testing.T) {
|
func TestExecuteSessionTaskGatewayDoesNotExportArtifactScopeDeclaredInOutput(t *testing.T) {
|
||||||
gateway := newAcpFakeOpenClawGateway(t)
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
gateway.artifactWorkspaceRoot = t.TempDir()
|
gateway.artifactWorkspaceRoot = t.TempDir()
|
||||||
defer gateway.Close()
|
defer gateway.Close()
|
||||||
@ -1846,29 +1847,17 @@ func TestExecuteSessionTaskGatewayExportsArtifactScopeDeclaredInOutput(t *testin
|
|||||||
t.Fatalf("expected gateway response, got rpc error: %#v", rpcErr)
|
t.Fatalf("expected gateway response, got rpc error: %#v", rpcErr)
|
||||||
}
|
}
|
||||||
if got := response["success"]; got != true {
|
if got := response["success"]; got != true {
|
||||||
t.Fatalf("expected output-declared artifact to satisfy export, got %#v", response)
|
t.Fatalf("expected text-only response to complete without adopting output-declared artifact, got %#v", response)
|
||||||
}
|
}
|
||||||
if got := gateway.ArtifactExportCount(); got != 2 {
|
if got := gateway.ArtifactExportCount(); got != 1 {
|
||||||
t.Fatalf("expected prepared scope export then output scope fallback export, got %d", got)
|
t.Fatalf("expected only current prepared scope export, got %d", got)
|
||||||
}
|
}
|
||||||
artifacts := responseArtifactMaps(t, response)
|
if artifacts, ok := response["artifacts"]; ok {
|
||||||
if len(artifacts) != 1 {
|
t.Fatalf("expected no artifact from output-declared path, got %#v", artifacts)
|
||||||
t.Fatalf("expected one output-declared artifact, got %#v", artifacts)
|
|
||||||
}
|
|
||||||
if got := artifacts[0]["relativePath"]; got != "AI_Agent_News_June_2_2026.md" {
|
|
||||||
t.Fatalf("expected artifact relative path from fallback scope, got %#v", artifacts[0])
|
|
||||||
}
|
|
||||||
downloadURL := strings.TrimSpace(shared.StringArg(artifacts[0], "downloadUrl", ""))
|
|
||||||
parsedDownloadURL, err := url.Parse(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("parse downloadUrl: %v", err)
|
|
||||||
}
|
|
||||||
if got := parsedDownloadURL.Query().Get("artifactScope"); got != actualScope {
|
|
||||||
t.Fatalf("expected fallback artifact scope in downloadUrl, got %q", got)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteSessionTaskGatewayExportsDraftScopeVariant(t *testing.T) {
|
func TestExecuteSessionTaskGatewayDoesNotExportDraftScopeVariant(t *testing.T) {
|
||||||
gateway := newAcpFakeOpenClawGateway(t)
|
gateway := newAcpFakeOpenClawGateway(t)
|
||||||
gateway.artifactWorkspaceRoot = t.TempDir()
|
gateway.artifactWorkspaceRoot = t.TempDir()
|
||||||
defer gateway.Close()
|
defer gateway.Close()
|
||||||
@ -1913,14 +1902,13 @@ func TestExecuteSessionTaskGatewayExportsDraftScopeVariant(t *testing.T) {
|
|||||||
t.Fatalf("expected gateway response, got rpc error: %#v", rpcErr)
|
t.Fatalf("expected gateway response, got rpc error: %#v", rpcErr)
|
||||||
}
|
}
|
||||||
if got := response["success"]; got != true {
|
if got := response["success"]; got != true {
|
||||||
t.Fatalf("expected draft scope variant artifact to satisfy export, got %#v", response)
|
t.Fatalf("expected text-only task to complete without adopting draft variant artifact, got %#v", response)
|
||||||
}
|
}
|
||||||
if got := gateway.ArtifactExportCount(); got != 2 {
|
if got := gateway.ArtifactExportCount(); got != 1 {
|
||||||
t.Fatalf("expected prepared scope export then draft variant export, got %d", got)
|
t.Fatalf("expected only current prepared scope export, got %d", got)
|
||||||
}
|
}
|
||||||
artifacts := responseArtifactMaps(t, response)
|
if artifacts, ok := response["artifacts"]; ok {
|
||||||
if len(artifacts) != 1 || artifacts[0]["relativePath"] != "AI_Agent_News_June_2_2026.md" {
|
t.Fatalf("expected no artifact from draft scope variant, got %#v", artifacts)
|
||||||
t.Fatalf("expected artifact from draft scope variant, got %#v", artifacts)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1960,7 +1948,7 @@ func TestExecuteSessionMessageGatewayDoesNotRewriteClaimedArtifactsWithoutGatewa
|
|||||||
if gateway.ArtifactExportCount() != 1 {
|
if gateway.ArtifactExportCount() != 1 {
|
||||||
t.Fatalf("expected one post-run artifact export sync, got %d", gateway.ArtifactExportCount())
|
t.Fatalf("expected one post-run artifact export sync, got %d", gateway.ArtifactExportCount())
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2010,7 +1998,7 @@ func TestExecuteSessionMessageGatewayExportsArtifactsWithoutPromptHeuristic(t *t
|
|||||||
if got := strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", "")); got == "" {
|
if got := strings.TrimSpace(shared.StringArg(exportParams, "artifactScope", "")); got == "" {
|
||||||
t.Fatalf("expected bridge to export the prepared task artifact scope, got %#v", exportParams)
|
t.Fatalf("expected bridge to export the prepared task artifact scope, got %#v", exportParams)
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
t.Fatalf("expected connect, artifact prepare, chat.send, agent.wait, then artifact export, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2806,31 +2794,33 @@ func TestExtractArtifactPayloadsRejectsUnsafeDownloadURLArtifactNames(t *testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
type acpFakeOpenClawGateway struct {
|
type acpFakeOpenClawGateway struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
connectCount atomic.Int32
|
connectCount atomic.Int32
|
||||||
chatSendCount atomic.Int32
|
chatSendCount atomic.Int32
|
||||||
agentWaitCount atomic.Int32
|
agentWaitCount atomic.Int32
|
||||||
artifactPrepareCount atomic.Int32
|
artifactPrepareCount atomic.Int32
|
||||||
artifactCount atomic.Int32
|
artifactSnapshotCount atomic.Int32
|
||||||
artifactReadCount atomic.Int32
|
artifactCount atomic.Int32
|
||||||
artifactReadFailures atomic.Int32
|
artifactReadCount atomic.Int32
|
||||||
closeNextChatSend atomic.Bool
|
artifactReadFailures atomic.Int32
|
||||||
alwaysCloseChatSend atomic.Bool
|
closeNextChatSend atomic.Bool
|
||||||
agentWaitDelayMs atomic.Int64
|
alwaysCloseChatSend atomic.Bool
|
||||||
largeGatewayPayloadBytes atomic.Int64
|
agentWaitDelayMs atomic.Int64
|
||||||
emitAgentDelta atomic.Bool
|
largeGatewayPayloadBytes atomic.Int64
|
||||||
lastConnectClient atomic.Value
|
emitAgentDelta atomic.Bool
|
||||||
lastChatSendParams atomic.Value
|
lastConnectClient atomic.Value
|
||||||
lastArtifactPrepareParams atomic.Value
|
lastChatSendParams atomic.Value
|
||||||
lastArtifactExportParams atomic.Value
|
lastArtifactPrepareParams atomic.Value
|
||||||
lastAgentWaitParams atomic.Value
|
lastArtifactSnapshotParams atomic.Value
|
||||||
mu sync.Mutex
|
lastArtifactExportParams atomic.Value
|
||||||
methods []string
|
lastAgentWaitParams atomic.Value
|
||||||
runMessages map[string]string
|
mu sync.Mutex
|
||||||
artifactMode string
|
methods []string
|
||||||
artifactWorkspaceRoot string
|
runMessages map[string]string
|
||||||
alternateRunID string
|
artifactMode string
|
||||||
|
artifactWorkspaceRoot string
|
||||||
|
alternateRunID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway {
|
func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway {
|
||||||
@ -3123,6 +3113,28 @@ func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway {
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
case "xworkmate.artifacts.collect-and-snapshot":
|
||||||
|
fake.artifactSnapshotCount.Add(1)
|
||||||
|
params := shared.AsMap(frame["params"])
|
||||||
|
fake.lastArtifactSnapshotParams.Store(params)
|
||||||
|
runID := strings.TrimSpace(shared.StringArg(params, "runId", "fake-run"))
|
||||||
|
sessionKey := strings.TrimSpace(shared.StringArg(params, "sessionKey", ""))
|
||||||
|
artifactScope := strings.TrimSpace(shared.StringArg(params, "artifactScope", ""))
|
||||||
|
_ = conn.WriteJSON(map[string]any{
|
||||||
|
"type": "res",
|
||||||
|
"id": id,
|
||||||
|
"ok": true,
|
||||||
|
"payload": map[string]any{
|
||||||
|
"runId": runID,
|
||||||
|
"sessionKey": sessionKey,
|
||||||
|
"remoteWorkingDirectory": "/remote/openclaw/workspace",
|
||||||
|
"remoteWorkspaceRefKind": "remotePath",
|
||||||
|
"artifactScope": artifactScope,
|
||||||
|
"scopeKind": "task",
|
||||||
|
"copiedFiles": []any{},
|
||||||
|
"warnings": []any{},
|
||||||
|
},
|
||||||
|
})
|
||||||
case "xworkmate.artifacts.export":
|
case "xworkmate.artifacts.export":
|
||||||
fake.artifactCount.Add(1)
|
fake.artifactCount.Add(1)
|
||||||
if fake.artifactMode == "unknown" {
|
if fake.artifactMode == "unknown" {
|
||||||
@ -3203,6 +3215,42 @@ func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
runMessage := fake.runMessage(runID)
|
||||||
|
lowerRunMessage := strings.ToLower(runMessage)
|
||||||
|
hallucinatedFiles := strings.Contains(runMessage, "hallucinate-files")
|
||||||
|
if !hallucinatedFiles && (strings.Contains(runMessage, "7张") || strings.Contains(runMessage, "图片") || strings.Contains(lowerRunMessage, "image")) {
|
||||||
|
payload["artifacts"] = appendArtifactList(payload["artifacts"], []any{map[string]any{
|
||||||
|
"relativePath": "artifacts/media/browser/series-01.png",
|
||||||
|
"label": "series-01.png",
|
||||||
|
"contentType": "image/png",
|
||||||
|
"sizeBytes": 12,
|
||||||
|
"sha256": "fake-sha256",
|
||||||
|
"artifactScope": artifactScope,
|
||||||
|
"scopeKind": "task",
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
if !hallucinatedFiles && !strings.Contains(runMessage, "make pdf artifact") && strings.Contains(lowerRunMessage, "pdf") {
|
||||||
|
payload["artifacts"] = appendArtifactList(payload["artifacts"], []any{map[string]any{
|
||||||
|
"relativePath": "artifacts/tmp-openclaw/final.pdf",
|
||||||
|
"label": "final.pdf",
|
||||||
|
"contentType": "application/pdf",
|
||||||
|
"sizeBytes": 12,
|
||||||
|
"sha256": "fake-sha256",
|
||||||
|
"artifactScope": artifactScope,
|
||||||
|
"scopeKind": "task",
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
if !hallucinatedFiles && (strings.Contains(runMessage, "视频") || strings.Contains(lowerRunMessage, "video")) {
|
||||||
|
payload["artifacts"] = appendArtifactList(payload["artifacts"], []any{map[string]any{
|
||||||
|
"relativePath": "artifacts/tmp-openclaw/final.mp4",
|
||||||
|
"label": "final.mp4",
|
||||||
|
"contentType": "video/mp4",
|
||||||
|
"sizeBytes": 12,
|
||||||
|
"sha256": "fake-sha256",
|
||||||
|
"artifactScope": artifactScope,
|
||||||
|
"scopeKind": "task",
|
||||||
|
}})
|
||||||
|
}
|
||||||
if len(filesystemArtifacts) > 0 {
|
if len(filesystemArtifacts) > 0 {
|
||||||
payload["artifacts"] = appendArtifactList(payload["artifacts"], filesystemArtifacts)
|
payload["artifacts"] = appendArtifactList(payload["artifacts"], filesystemArtifacts)
|
||||||
}
|
}
|
||||||
@ -3443,6 +3491,15 @@ func (f *acpFakeOpenClawGateway) LastArtifactPrepareParams() map[string]any {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *acpFakeOpenClawGateway) ArtifactSnapshotCount() int {
|
||||||
|
return int(f.artifactSnapshotCount.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *acpFakeOpenClawGateway) LastArtifactSnapshotParams() map[string]any {
|
||||||
|
params, _ := f.lastArtifactSnapshotParams.Load().(map[string]any)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
func (f *acpFakeOpenClawGateway) ArtifactExportCount() int {
|
func (f *acpFakeOpenClawGateway) ArtifactExportCount() int {
|
||||||
return int(f.artifactCount.Load())
|
return int(f.artifactCount.Load())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,8 @@ func NewServer() *Server {
|
|||||||
shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""),
|
shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""),
|
||||||
shared.EnvOrDefault("BRIDGE_REVIEW_AUTH_TOKEN", ""),
|
shared.EnvOrDefault("BRIDGE_REVIEW_AUTH_TOKEN", ""),
|
||||||
),
|
),
|
||||||
openClawGate: newOpenClawGatewayAdmissionGate(config),
|
openClawGate: newOpenClawGatewayAdmissionGate(config),
|
||||||
|
openClawSessions: NewThreadSessionMapper(),
|
||||||
taskRouter: newDistributedTaskRouter(distributedTaskRouterConfig{
|
taskRouter: newDistributedTaskRouter(distributedTaskRouterConfig{
|
||||||
Config: config,
|
Config: config,
|
||||||
Token: resolveDistributedTaskForwardToken(config),
|
Token: resolveDistributedTaskForwardToken(config),
|
||||||
|
|||||||
@ -96,11 +96,12 @@ type Server struct {
|
|||||||
orchestrator *SessionOrchestrator
|
orchestrator *SessionOrchestrator
|
||||||
memoryService memory.Service
|
memoryService memory.Service
|
||||||
|
|
||||||
providerOrder []string
|
providerOrder []string
|
||||||
gateway *gatewayruntime.Manager
|
gateway *gatewayruntime.Manager
|
||||||
openClawGate *openClawGatewayAdmissionGate
|
openClawGate *openClawGatewayAdmissionGate
|
||||||
jobs *jobManager
|
openClawSessions *ThreadSessionMapper
|
||||||
taskRouter *distributedTaskRouter
|
jobs *jobManager
|
||||||
|
taskRouter *distributedTaskRouter
|
||||||
|
|
||||||
// Legacy / Common
|
// Legacy / Common
|
||||||
authService interface{} // Minimal auth dependency
|
authService interface{} // Minimal auth dependency
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import (
|
|||||||
"xworkmate-bridge/internal/shared"
|
"xworkmate-bridge/internal/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func sseFirstResultEnvelope(t *testing.T, body string) map[string]any {
|
func sseFirstResultEnvelope(t *testing.T, body string) map[string]any {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, rawLine := range strings.Split(body, "\n") {
|
for _, rawLine := range strings.Split(body, "\n") {
|
||||||
@ -720,7 +719,7 @@ func TestHTTPHandlerGatewayOpenClawFiltersRawGatewayEventsAndKeepsFinalResult(t
|
|||||||
if !strings.Contains(fmt.Sprint(result), openClawArtifactDownloadPath) {
|
if !strings.Contains(fmt.Sprint(result), openClawArtifactDownloadPath) {
|
||||||
t.Fatalf("expected normalized artifact download URL in task result, got %#v", result)
|
t.Fatalf("expected normalized artifact download URL in task result, got %#v", result)
|
||||||
}
|
}
|
||||||
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.export"}) {
|
if got := gateway.Methods(); !sameMethods(got, []string{"connect", "xworkmate.artifacts.prepare", "chat.send", "agent.wait", "xworkmate.artifacts.collect-and-snapshot", "xworkmate.artifacts.export"}) {
|
||||||
t.Fatalf("expected artifact workflow methods to prepare before chat.send, got %#v", got)
|
t.Fatalf("expected artifact workflow methods to prepare before chat.send, got %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user