chore: add repomix-output.xml to .gitignore
This commit is contained in:
parent
d436729508
commit
1f43a989a0
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ build/
|
||||
.env
|
||||
xworkmate-bridge
|
||||
xworkmate-go-core-linux
|
||||
repomix-output.xml
|
||||
|
||||
17
docs/architecture/README.md
Normal file
17
docs/architecture/README.md
Normal 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
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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"} {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user