- Add 4 chain maps: task-execution, artifact-lifecycle, session-recovery, bridge-distributed - Add cross-repo call analysis with top-10 fragile points - Update AGENTS.md with 'Cross-Repo Architecture Chain Maps' section - Document artifact path gap: OpenClaw tools output to ~/.openclaw/media/ but plugin export scans tasks/<session>/<run>/
10 KiB
10 KiB
Chain Map: Task Execution (App → Bridge → OpenClaw → Plugin)
Repo chain: xworkmate-app → xworkmate-bridge → openclaw.svc.plus → openclaw-multi-session-plugins
Entry Points (xworkmate-app)
1. AssistantPage.sendChatMessage()
lib/features/assistant/assistant_page_state_actions.dart
2. AppController.resendChatMessage() (retry)
lib/app/app_controller_desktop_thread_actions.dart
3. drainOpenClawGatewayQueue() (queue drain)
lib/app/app_controller_openclaw_task_queue.dart
Call Flow
xworkmate-app
AssistantPage.sendChatMessage()
└─ AppController.sendChatMessage()
├─ Resolve/create TaskThread by sessionKey/threadId
├─ Prepare local workspace: ~/.xworkmate/threads/<session>/
├─ Build task context prompt (sessionKey, workspace, contract)
├─ Attach metadata.xworkmateTaskArtifactContract
└─ Select execution path:
├─ Agent providers (codex/opencode/gemini/hermes)
│ └─ DesktopGoTaskService.startSession()
└─ OpenClaw gateway
├─ Check admission: isOpenClawLaneIdle()
│ ├─ Idle → send immediately
│ └─ Busy → queueOpenClawGatewayWork()
└─ DesktopGoTaskService.startSession()
DesktopGoTaskService
└─ ExternalCodeAgentAcpDesktopTransport.executeTask()
├─ acp.capabilities → verify provider availability
├─ xworkmate.routing.resolve → pre-resolve route
├─ session.start (WebSocket /acp)
│ ├─ params: sessionId, threadId, prompt, workingDirectory
│ ├─ routing: executionTarget=gateway, preferredGatewayProviderId=openclaw
│ └─ metadata: xworkmateTaskArtifactContract
└─ Listen for SSE session.update events
├─ status=running → poll xworkmate.tasks.get
├─ terminal snapshot → applyGatewayChatResult()
└─ artifacts → sync to local workspace
───────────────────────────────────────────────────────────
Protocol: ACP JSON-RPC 2.0 over WebSocket
Path: wss://xworkmate-bridge.svc.plus/acp
Auth: Bearer <BRIDGE_AUTH_TOKEN>
───────────────────────────────────────────────────────────
xworkmate-bridge
internal/acp/http_handler.go: WebSocket handler
└─ handleRequest() (rpc_handler.go)
├─ Validate routing params
├─ forceOpenClawGatewayRequest() — ensure gateway routing
└─ orchestrator.Process()
├─ Routing engine: Resolve(params, prompt, memory)
│ ├─ Heuristic: looksLocal() / looksOnline()
│ ├─ Memory preferences
│ └─ LLM classifier fallback
│
├─ openClawGatewayAdmissionGate.acquire()
│ ├─ maxActive: 5, maxQueued: 20
│ ├─ queueTimeout: 10 min
│ └─ Returns: admission slot or OPENCLAW_GATEWAY_BUSY
│
└─ startOpenClawGatewayTask()
├─ ensureProductionGatewayConnected()
├─ openClawArtifactPrepare()
│ └─ gateway.request('xworkmate.artifacts.prepare')
│ └─ scope: tasks/<sessionKey>/<runId>/
│
├─ gateway.request('chat.send')
│ └─ payload: message, attachments, artifactSince, workingDirectory
│
├─ Create OpenClawTaskRecord
│ ├─ SessionID, ThreadID, TurnID, RunID
│ ├─ SessionKey (from gateway response)
│ ├─ TaskLoadClass (short_task/long_task/complex_chain_task)
│ ├─ RuntimeBudgetMinutes (10/30/60)
│ └─ PreparedArtifact scope ref
│
└─ startOpenClawTaskMonitor()
└─ Every 1s: probeOpenClawTask()
└─ gateway.request('agent.wait', timeout=1s)
├─ completed → completeOpenClawTask()
├─ failed → failOpenClawTask()
├─ SLA expired → TASK_SLA_EXPIRED
└─ silent failure >10min → cleanup
───────────────────────────────────────────────────────────
Protocol: Custom JSON-RPC v4 over WebSocket
Auth: Ed25519 device identity + shared token
───────────────────────────────────────────────────────────
openclaw.svc.plus
Gateway: receives 'chat.send'
└─ Agent runner processes turn
├─ Model inference (Claude/GPT/Gemini)
├─ Tool execution loop
│ ├─ browser → screenshot → saveMediaBuffer(browser)
│ ├─ image-generation → GeneratedImageAsset (in-memory)
│ ├─ file-write → write to working directory or arbitrary path
│ └─ video-render → output to media dir or /tmp
│
└─ Tool output destinations:
├─ ~/.openclaw/media/browser/<uuid>.png ← NOT in tasks/<session>/<run>/
├─ ~/.openclaw/media/inbound/<uuid>.<ext> ← NOT in tasks/<session>/<run>/
├─ /tmp/openclaw/downloads/ ← NOT in tasks/<session>/<run>/
└─ tasks/<session>/<run>/output.md ← MAY be written here
openclaw-multi-session-plugins
Receives gateway RPC: xworkmate.artifacts.prepare
prepareXWorkmateArtifacts()
├─ resolveWorkspaceDir() → workspace root
├─ safeScopeSegment(sessionKey) → sanitize
├─ safeScopeSegment(runId) → sanitize
└─ mkdir <workspace>/tasks/<safeSessionKey>/<safeRunId>/
Receives gateway RPC: xworkmate.artifacts.export
exportXWorkmateArtifacts()
├─ resolveScopeRoot(workspaceRoot, artifactScope)
├─ collectCandidates(scopeRoot)
│ ├─ Walk tasks/<session>/<run>/ recursively ← ONLY THIS DIRECTORY
│ ├─ Skip symlinks
│ ├─ Skip .git, .openclaw, node_modules, etc.
│ └─ Apply artifact-ignore.md rules
│
├─ Read each candidate file, compute SHA-256
├─ signArtifactRef(sessionKey, runId, relativePath)
└─ Return manifest + base64 file contents
───────────────────────────────────────────────────────────
CRITICAL GAP: Tool outputs in ~/.openclaw/media/* or /tmp/*
are NOT in tasks/<session>/<run>/ → NOT exported → NOT visible
to bridge → NOT synced to app.
───────────────────────────────────────────────────────────
Back to xworkmate-bridge:
completeOpenClawTask()
├─ Call xworkmate.artifacts.export via gateway
├─ Collect artifact manifest
├─ Build terminal snapshot with:
│ ├─ status: completed/failed/cancelled
│ ├─ artifacts: { items: [...], scope: "..." }
│ └─ text: final output
│
├─ decorateOpenClawArtifactDownloadURLs()
│ └─ Replace artifact refs with signed download URLs
│ Format: /artifacts/openclaw/download?ref=<signed>&t=<expiry>
│
└─ Send SSE session.update to app
Back to xworkmate-app:
ExternalCodeAgentAcpDesktopTransport
├─ Receive terminal snapshot via SSE or xworkmate.tasks.get
├─ applyGatewayChatResult()
│ ├─ success=true && artifacts present → syncArtifactsFromBridge()
│ ├─ success=false → lastResultCode=failed
│ └─ no-exported-artifacts → lastArtifactSyncStatus=no-exported-artifacts
│
└─ syncArtifactsFromBridge()
└─ Download each artifact via /artifacts/openclaw/download
→ Save to ~/.xworkmate/threads/<session>/
Key Files by Repo
xworkmate-app
lib/runtime/external_code_agent_acp_desktop_transport.dart— 核心 ACP transportlib/runtime/go_task_service_client.dart— GoTaskService 数据模型lib/runtime/gateway_acp_client.dart— ACP HTTP RPC clientlib/app/app_controller_openclaw_task_queue.dart— OpenClaw 并发队列lib/app/app_controller_desktop_thread_sessions.dart— session 恢复逻辑lib/app/app_controller_desktop_thread_actions.dart— 消息发送lib/runtime/agent_registry.dart— agent registry
xworkmate-bridge
internal/acp/http_handler.go— HTTP server + WebSocket handlerinternal/acp/rpc_handler.go— JSON-RPC dispatcherinternal/acp/orchestrator.go— 会话编排 (2000+ lines)internal/acp/openclaw_gateway_admission.go— 并发控制internal/acp/openclaw_async_tasks.go— 异步任务管理internal/acp/openclaw_artifact_download.go— artifact 下载代理internal/gatewayruntime/runtime.go— gateway WebSocket clientinternal/router/router.go— 路由引擎
openclaw-multi-session-plugins
src/exportArtifacts.ts— artifact prepare/export/read (963 lines)index.ts— plugin entry + gateway method registrationsrc/bridgeAgents.ts— bridge agent orchestration
openclaw.svc.plus (reference)
src/config/paths.ts— ~/.openclaw/ state directorysrc/infra/tmp-openclaw-dir.ts— /tmp/openclaw/dist/store-ezT1dexf.js— saveMediaBuffer() → media//
Fragile Points
- F1: Tool output path mismatch — Tools save to media/, plugin exports from tasks/ → gap
- F2: Session key mismatch — App/Bridge/Plugin must agree on sessionKey format
- F3: Prepare timing — If prepare fails after send, no scope directory exists
- F4: Admission gate rejection — Queue full → OPENCLAW_GATEWAY_BUSY → app must handle
- F5: Bridge restart — In-memory sessions lost → app must detect and recover
- F6: Artifact ref key rotation — Secret change invalidates all signed refs
- F7: SSE stream interruption — Polling fallback must have correct timeout/retry