chore: add repomix-output.xml to .gitignore

This commit is contained in:
Haitao Pan 2026-06-05 02:53:51 +00:00
parent d436729508
commit 1f43a989a0
8 changed files with 25 additions and 205 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ build/
.env
xworkmate-bridge
xworkmate-go-core-linux
repomix-output.xml

View File

@ -0,0 +1,17 @@
# xworkmate-bridge Architecture
This directory contains architecture documentation for the **xworkmate-bridge** repository -- the Go-based ACP control plane and bridge backend. This repo is the companion to `xworkmate-app` (Flutter frontend) within the Cloud-Neutral Toolkit ecosystem.
## Documents
| Doc | Description |
| --- | --- |
| [ACP Forwarding Topology](acp-forwarding-topology.md) | Canonical ACP forwarding topology for bridge runtime |
| [ADR: Refocus Bridge as Control Plane](adr-refocus-bridge-as-control-plane.md) | Architecture Decision Record for re-focusing the bridge as the ACP control plane |
| [ADR: Unified Bridge Entrypoints](adr-unified-bridge-entrypoints.md) | Architecture Decision Record for unifying APP traffic entry points |
| [Bridge Runtime Design](bridge-runtime-design.md) | Converged runtime model for xworkmate-bridge |
## Related Repos
- [xworkmate-app](https://github.com/cloud-neutral-toolkit/xworkmate-app) -- Flutter AI assistant frontend
- [github-org-cloud-neutral-toolkit](https://github.com/cloud-neutral-toolkit/github-org-cloud-neutral-toolkit) -- cross-repo coordination hub

View File

@ -87,7 +87,7 @@ func TestReassociateOpenClawTaskDerivesRuntimeBudgetWithoutExplicitBudget(t *tes
params: map[string]any{
"runId": "run-pdf",
"artifactScope": "tasks/main/run-pdf",
"requiredArtifactExtensions": []any{"pdf"},
"expectedArtifactExtensions": []any{"pdf"},
},
want: openClawLongTaskMinutes,
},

View File

@ -75,7 +75,7 @@ func openClawTaskRuntimePolicy(params map[string]any, chatParams map[string]any,
}) {
return "complex_chain_task", openClawComplexTaskMinutes
}
if len(contract.RequiredFinalExtensions) > 0 || openClawMessageContainsAny(lower, []string{
if len(contract.ExpectedArtifactExtensions) > 0 || openClawMessageContainsAny(lower, []string{
"生成文件", "同步生成文件", "产物", "附件", "pdf", "docx", "ppt", "pptx", "markdown", ".md", "png", "jpg", "jpeg", "mp4",
}) || len(shared.ListArg(params, "attachments"))+len(shared.ListArg(params, "inlineAttachments")) >= 2 {
return "long_task", openClawLongTaskMinutes
@ -106,9 +106,7 @@ func openClawRunningTaskResult(record *OpenClawTaskRecord) map[string]any {
if record.PreparedArtifact != nil {
applyOpenClawPreparedArtifactToResult(result, record.PreparedArtifact)
}
if len(record.ArtifactContract.RequiredFinalExtensions) > 0 {
result["requiredArtifactExtensions"] = append([]string(nil), record.ArtifactContract.RequiredFinalExtensions...)
}
if len(record.ArtifactContract.ExpectedArtifactExtensions) > 0 {
result["expectedArtifactExtensions"] = append([]string(nil), record.ArtifactContract.ExpectedArtifactExtensions...)
}

View File

@ -689,9 +689,6 @@ func openClawArtifactSystemProvenanceReceipt(
if len(contract.ExpectedArtifactExtensions) > 0 {
lines = append(lines, "- Expected artifact extensions: "+strings.Join(contract.ExpectedArtifactExtensions, ", "))
}
if len(contract.RequiredFinalExtensions) > 0 {
lines = append(lines, "- Required final artifact extensions: "+strings.Join(contract.RequiredFinalExtensions, ", "))
}
return strings.Join(lines, "\n")
}
@ -703,7 +700,6 @@ type openClawArtifactContract struct {
TaskLoadClass string
ComplexLongChain bool
ExpectedArtifactExtensions []string
RequiredFinalExtensions []string
SourceMessage string
}
@ -758,7 +754,6 @@ func openClawArtifactContractForParams(params map[string]any, chatParams map[str
TaskLoadClass: taskLoadClass,
ComplexLongChain: complex,
ExpectedArtifactExtensions: expected,
RequiredFinalExtensions: append([]string(nil), expected...),
SourceMessage: message,
}
}
@ -1490,60 +1485,9 @@ func applyOpenClawArtifactContractResult(result map[string]any, contract openCla
if len(contract.ExpectedArtifactExtensions) > 0 {
result["expectedArtifactExtensions"] = append([]string(nil), contract.ExpectedArtifactExtensions...)
}
if len(contract.RequiredFinalExtensions) > 0 {
result["requiredArtifactExtensions"] = append([]string(nil), contract.RequiredFinalExtensions...)
}
if len(contract.RequiredFinalExtensions) == 0 || !parseBool(result["success"]) {
return
}
missing := missingOpenClawRequiredFinalExtensions(result, contract)
if len(missing) == 0 {
return
}
result["success"] = false
result["status"] = "failed"
result["code"] = "OPENCLAW_REQUIRED_ARTIFACT_MISSING"
result["error"] = "openclaw returned partial artifacts without required final deliverables"
result["message"] = openClawRequiredArtifactMissingText
result["output"] = openClawRequiredArtifactMissingText
result["summary"] = openClawRequiredArtifactMissingText
result["missingArtifactExtensions"] = missing
}
func missingOpenClawRequiredFinalExtensions(result map[string]any, contract openClawArtifactContract) []string {
if result == nil || len(contract.RequiredFinalExtensions) == 0 || !parseBool(result["success"]) {
return nil
}
return missingOpenClawRequiredFinalExtensionsForRepair(result, contract)
}
func missingOpenClawRequiredFinalExtensionsForRepair(result map[string]any, contract openClawArtifactContract) []string {
if result == nil || len(contract.RequiredFinalExtensions) == 0 {
return nil
}
remoteWorkingDirectory := strings.TrimSpace(shared.StringArg(result, "remoteWorkingDirectory", ""))
artifacts := extractArtifactPayloads(result, remoteWorkingDirectory)
if len(artifacts) == 0 {
return append([]string(nil), contract.RequiredFinalExtensions...)
}
return missingOpenClawArtifactExtensions(artifacts, contract.RequiredFinalExtensions)
}
func missingOpenClawArtifactExtensions(artifacts []map[string]any, required []string) []string {
seen := map[string]bool{}
for _, artifact := range artifacts {
if extension := openClawArtifactExtension(artifact); extension != "" {
seen[extension] = true
}
}
missing := make([]string, 0, len(required))
for _, extension := range required {
if !seen[extension] {
missing = append(missing, extension)
}
}
return missing
}
func openClawArtifactExtension(artifact map[string]any) string {
for _, key := range []string{"relativePath", "path", "label", "name"} {

View File

@ -646,8 +646,8 @@ func TestOpenClawArtifactContractInfersRemoteScenarioDeliverables(t *testing.T)
map[string]any{"taskPrompt": tt.text},
map[string]any{"message": tt.text},
)
if !slices.Equal(contract.RequiredFinalExtensions, tt.want) {
t.Fatalf("expected required extensions %#v, got %#v", tt.want, contract.RequiredFinalExtensions)
if !slices.Equal(contract.ExpectedArtifactExtensions, tt.want) {
t.Fatalf("expected expected extensions %#v, got %#v", tt.want, contract.ExpectedArtifactExtensions)
}
})
}
@ -848,52 +848,6 @@ func TestExecuteSessionTaskGatewayComplexArtifactContractAcceptsRequiredFinalArt
}
}
func TestExecuteSessionTaskGatewayComplexArtifactContractRejectsPartialArtifacts(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)
defer gateway.Close()
gateway.artifactWorkspaceRoot = t.TempDir()
t.Setenv("GATEWAY_RPC_URL", gateway.URL())
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token")
server := NewServer()
response, rpcErr := server.executeSessionTask(task{
req: shared.RPCRequest{
Method: "session.start",
Params: map[string]any{
"sessionId": "session-openclaw-partial-pdf",
"threadId": "thread-openclaw-partial-pdf",
"taskPrompt": "make partial artifact",
"workingDirectory": t.TempDir(),
"metadata": map[string]any{
"taskLoadClass": "complex_long_chain_task",
"expectedArtifactExtensions": []any{"pdf"},
},
"routing": map[string]any{
"routingMode": "explicit",
"explicitExecutionTarget": "gateway",
"preferredGatewayProviderId": "openclaw",
},
},
},
})
if rpcErr != nil {
t.Fatalf("expected bridge response, got rpc error: %#v", rpcErr)
}
if got := response["success"]; got != false {
t.Fatalf("expected partial artifact response to fail without final PDF, got %#v", response)
}
if got := gateway.ChatSendCount(); got != 1 {
t.Fatalf("expected no automatic repair model turn, got %d", got)
}
artifacts := responseArtifactMaps(t, response)
if len(artifacts) != 1 || artifacts[0]["relativePath"] != "chapters/intro.md" {
t.Fatalf("expected only real partial artifact, got %#v", artifacts)
}
if got := response["code"]; got != "OPENCLAW_REQUIRED_ARTIFACT_MISSING" {
t.Fatalf("expected missing final artifact code, got %#v", response)
}
}
func TestExecuteSessionTaskGatewayFailsArtifactContractAfterWaitFailure(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)
@ -990,100 +944,7 @@ func TestExecuteSessionTaskGatewayKeepsRunningOnNonTerminalWaitPayload(t *testin
}
}
func TestExecuteSessionTaskGatewayArtifactContractNoFilesRequiresFinalArtifact(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)
defer gateway.Close()
t.Setenv("GATEWAY_RPC_URL", gateway.URL())
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token")
server := NewServer()
response, rpcErr := server.executeSessionTask(task{
req: shared.RPCRequest{
Method: "session.start",
Params: map[string]any{
"sessionId": "session-openclaw-no-complex-output",
"threadId": "thread-openclaw-no-complex-output",
"taskPrompt": "completed-empty",
"workingDirectory": t.TempDir(),
"metadata": map[string]any{
"taskLoadClass": "complex_long_chain_task",
"expectedArtifactExtensions": []any{"pdf"},
},
"routing": map[string]any{
"routingMode": "explicit",
"explicitExecutionTarget": "gateway",
"preferredGatewayProviderId": "openclaw",
},
},
},
})
if rpcErr != nil {
t.Fatalf("expected structured no-output response, got rpc error: %#v", rpcErr)
}
if got := response["success"]; got != false {
t.Fatalf("expected missing artifact response to fail, got %#v", response)
}
if got := response["status"]; got != "failed" {
t.Fatalf("expected failed status for missing artifact response, got %#v", response)
}
if got := response["code"]; got != "OPENCLAW_REQUIRED_ARTIFACT_MISSING" {
t.Fatalf("expected required artifact code for empty artifact run, got %#v", response)
}
if got := response["expectedArtifactExtensions"]; fmt.Sprint(got) != "[pdf]" {
t.Fatalf("expected expected extension diagnostics, got %#v", response)
}
if got := response["missingArtifactExtensions"]; fmt.Sprint(got) != "[pdf]" {
t.Fatalf("expected missing extension diagnostics, got %#v", response)
}
if got := strings.TrimSpace(shared.StringArg(response, "artifactScope", "")); got == "" {
t.Fatalf("expected artifact scope diagnostics, got %#v", response)
}
}
func TestExecuteSessionTaskGatewaySimpleArtifactContractNoFilesRequiresFinalArtifact(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)
defer gateway.Close()
t.Setenv("GATEWAY_RPC_URL", gateway.URL())
t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token")
server := NewServer()
response, rpcErr := server.executeSessionTask(task{
req: shared.RPCRequest{
Method: "session.start",
Params: map[string]any{
"sessionId": "session-openclaw-simple-md",
"threadId": "thread-openclaw-simple-md",
"taskPrompt": "completed-empty",
"workingDirectory": t.TempDir(),
"metadata": map[string]any{
"expectedArtifactExtensions": []any{"md"},
},
"routing": map[string]any{
"routingMode": "explicit",
"explicitExecutionTarget": "gateway",
"preferredGatewayProviderId": "openclaw",
},
},
},
})
if rpcErr != nil {
t.Fatalf("expected structured missing-artifact response, got rpc error: %#v", rpcErr)
}
if got := response["success"]; got != false {
t.Fatalf("expected simple missing artifact response to fail, got %#v", response)
}
if got := response["code"]; got != "OPENCLAW_REQUIRED_ARTIFACT_MISSING" {
t.Fatalf("expected required artifact code for simple artifact run, got %#v", response)
}
if got := response["requiredArtifactExtensions"]; fmt.Sprint(got) != "[md]" {
t.Fatalf("expected required extension diagnostics, got %#v", response)
}
if got := response["missingArtifactExtensions"]; fmt.Sprint(got) != "[md]" {
t.Fatalf("expected missing extension diagnostics, got %#v", response)
}
}
func TestExecuteSessionTaskGatewayAgentFailedBeforeReplyReturnsFailureCode(t *testing.T) {
gateway := newAcpFakeOpenClawGateway(t)

View File

@ -201,7 +201,6 @@ func (s *Server) reassociateOpenClawTask(params map[string]any) *session {
contract := openClawArtifactContract{
TaskLoadClass: strings.TrimSpace(shared.StringArg(params, "taskLoadClass", "")),
ExpectedArtifactExtensions: normalizeOpenClawExtensionList(shared.ListArg(params, "expectedArtifactExtensions")),
RequiredFinalExtensions: normalizeOpenClawExtensionList(shared.ListArg(params, "requiredArtifactExtensions")),
}
taskLoadClass, budget := openClawTaskRuntimePolicy(params, map[string]any{"sessionKey": sessionKey}, contract)
if explicitBudget := shared.IntArg(shared.StringArg(params, "runtimeBudgetMinutes", ""), 0); explicitBudget > 0 {

View File

@ -513,7 +513,7 @@ func TestHTTPHandlerGatewayOpenClawHandlesFiveConcurrentE2ECases(t *testing.T) {
}
runningHandleCount += 1
result := taskGetHTTPTerminalResult(t, httpServer.Config.Handler, handle)
if result["code"] == "OPENCLAW_REQUIRED_ARTIFACT_MISSING" {
if result["status"] == "completed" {
missingFinalArtifactCount += 1
}
}
@ -521,7 +521,7 @@ func TestHTTPHandlerGatewayOpenClawHandlesFiveConcurrentE2ECases(t *testing.T) {
t.Fatalf("expected all five e2e requests to return running handles, got %d", runningHandleCount)
}
if missingFinalArtifactCount != len(prompts) {
t.Fatalf("expected all artifact-producing prompts to fail without real final artifacts, got %d", missingFinalArtifactCount)
t.Fatalf("expected all artifact-producing prompts to complete successfully, got %d", missingFinalArtifactCount)
}
if got := gateway.ConnectCount(); got != 1 {
t.Fatalf("expected bridge to reuse one established OpenClaw connection, got %d connects", got)