Add OpenClaw thin adapter refactor plan
This commit is contained in:
parent
046020d8d7
commit
495af092c5
415
docs/plans/2026-06-05-openclaw-thin-adapter-refactor.md
Normal file
415
docs/plans/2026-06-05-openclaw-thin-adapter-refactor.md
Normal file
@ -0,0 +1,415 @@
|
||||
# OpenClaw Thin Adapter Refactor Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Reduce `openclaw-multi-session-plugins` from a task/session/event owner to a thin adapter while preserving XWorkmate task awareness, artifact isolation, and OpenClaw Gateway E2E behavior.
|
||||
|
||||
**Architecture:** `openclaw.svc.plus` is upstream and read-only. XWorkmate must carry typed metadata through App -> Bridge -> Plugin gateway methods, persist the app/OpenClaw session mapping in `SessionEntry.pluginExtensions` through existing OpenClaw runtime APIs, and query OpenClaw native task records instead of custom task stores. Bridge remains the App protocol adapter and event normalizer, not a task/session source of truth.
|
||||
|
||||
**Tech Stack:** Dart/Flutter (`xworkmate-app`), Go (`xworkmate-bridge`), TypeScript/Vitest (`openclaw-multi-session-plugins`), OpenClaw 2026.6.1 runtime APIs as read-only upstream.
|
||||
|
||||
---
|
||||
|
||||
## Hard Constraints
|
||||
|
||||
- Do not modify `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw.svc.plus`.
|
||||
- Do not rely on prompt text parsing for metadata.
|
||||
- Do not use `agent:main:${appThreadKey}` or `replace("agent:main:", "")` as the new-path session mapping.
|
||||
- Legacy string derivation is allowed only as one-time migration that writes a durable mapping.
|
||||
- Do not restore plugin-owned task DB, session DB, or event bus.
|
||||
- Validation must reference `docs/cases/`, especially `docs/cases/openclaw-gateway-e2e-regression/README.md`.
|
||||
|
||||
## Source-of-Truth Decision
|
||||
|
||||
- Task terminal state: OpenClaw native task registry when available.
|
||||
- Transcript/progress events: OpenClaw native transcript/runtime events, transported through Bridge/App SSE if needed.
|
||||
- Mapping truth: `SessionEntry.pluginExtensions[PLUGIN_ID]["xworkmate.sessionMapping"]`.
|
||||
- Artifact expected dirs: typed xworkmate metadata, mirrored into mapping and explicit artifact gateway params.
|
||||
|
||||
Because upstream `chat.send` rejects unknown fields and upstream `session_start` does not carry xworkmate metadata, Bridge must call a plugin gateway method before `chat.send` to upsert mapping and prepare artifacts. That method is the local adapter seam that avoids modifying OpenClaw.
|
||||
|
||||
## Target Metadata Shape
|
||||
|
||||
```ts
|
||||
type XWorkmateTaskMetadataV1 = {
|
||||
schemaVersion: 1;
|
||||
appThreadKey: string;
|
||||
openclawSessionKey?: string;
|
||||
expectedArtifactDirs: string[];
|
||||
requestId?: string;
|
||||
externalTaskId?: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
type XWorkmateSessionMappingV1 = {
|
||||
schemaVersion: 1;
|
||||
appThreadKey: string;
|
||||
openclawSessionKey: string;
|
||||
expectedArtifactDirs: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
source: "session_start" | "bridge_prepare" | "legacy_migration";
|
||||
legacyDerived?: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
## Task 1: Plugin Contract Tests for Durable Session Mapping
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/taskState.ts`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/index.ts`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/taskState.test.ts`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
|
||||
Add tests for:
|
||||
|
||||
- `recordXWorkmateSessionMapping` requires typed `appThreadKey`.
|
||||
- `openclawSessionKey` is taken from trusted gateway params/session scope, not derived from `appThreadKey`.
|
||||
- Mapping is written to `SessionEntry.pluginExtensions`.
|
||||
- Idempotent same mapping updates `updatedAt`.
|
||||
- Conflicting existing mapping fails closed.
|
||||
- Legacy derivation, if needed, writes `legacyDerived: true` and persists the mapping.
|
||||
|
||||
**Step 2: Run failing test**
|
||||
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins
|
||||
pnpm test src/taskState.test.ts
|
||||
```
|
||||
|
||||
Expected: FAIL until mapping helpers exist.
|
||||
|
||||
**Step 3: Implement minimal plugin mapping helper**
|
||||
|
||||
Implement helpers in `src/taskState.ts`:
|
||||
|
||||
- `normalizeXWorkmateTaskMetadataV1(input)`
|
||||
- `normalizeExpectedArtifactDirs(input)`
|
||||
- `upsertXWorkmateSessionMapping(api, metadata, openclawSessionKey, source)`
|
||||
- `readXWorkmateSessionMapping(api, lookup)`
|
||||
|
||||
Use `api.runtime.agent.session.getSessionEntry` and `api.runtime.agent.session.patchSessionEntry` if available. If runtime patch is unavailable, return structured `mapping_not_found` / `invalid_lookup`; do not silently fall back to an in-memory map.
|
||||
|
||||
**Step 4: Run tests**
|
||||
|
||||
```bash
|
||||
pnpm test src/taskState.test.ts
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 2: Plugin Gateway Method for Mapping + Artifact Prepare
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/index.ts`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/exportArtifacts.ts`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/exportArtifacts.test.ts`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
|
||||
Add/extend tests for a new gateway method, preferably `xworkmate.session.prepare`, that:
|
||||
|
||||
- accepts `{ schemaVersion, appThreadKey, openclawSessionKey, expectedArtifactDirs, runId?, requestId? }`;
|
||||
- rejects absolute paths and `..` in `expectedArtifactDirs`;
|
||||
- preserves expected dirs even when directories do not exist yet;
|
||||
- calls artifact prepare before agent execution;
|
||||
- returns mapping and artifact directory status.
|
||||
|
||||
**Step 2: Run failing test**
|
||||
|
||||
```bash
|
||||
pnpm test src/exportArtifacts.test.ts
|
||||
```
|
||||
|
||||
Expected: FAIL until gateway method is registered.
|
||||
|
||||
**Step 3: Implement the method**
|
||||
|
||||
In `index.ts`, register `xworkmate.session.prepare`. It should:
|
||||
|
||||
- call the mapping helper from Task 1;
|
||||
- call `prepareXWorkmateArtifacts`;
|
||||
- return `{ ok: true, mapping, artifactScope, artifactDirectory, expectedArtifactDirs }`.
|
||||
|
||||
Keep `xworkmate.artifacts.prepare/export/collect-and-snapshot` as thin artifact operations. Remove in-memory `createXWorkmateTaskStore()` as a required source of truth.
|
||||
|
||||
**Step 4: Run plugin suite**
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
pnpm typecheck
|
||||
pnpm pack:check
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 3: Plugin `xworkmate.tasks.get` Uses Native Task Registry
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/taskState.ts`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/index.ts`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/taskState.test.ts`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
|
||||
Cover inputs:
|
||||
|
||||
```ts
|
||||
{
|
||||
taskId?: string;
|
||||
runId?: string;
|
||||
appThreadKey?: string;
|
||||
openclawSessionKey?: string;
|
||||
includeArtifacts?: boolean;
|
||||
includeEvents?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Expected structured errors:
|
||||
|
||||
- `mapping_not_found`
|
||||
- `task_not_found`
|
||||
- `no_native_task_record`
|
||||
- `conflict`
|
||||
- `invalid_lookup`
|
||||
|
||||
**Step 2: Implement lookup**
|
||||
|
||||
Rules:
|
||||
|
||||
- `appThreadKey -> openclawSessionKey` must read pluginExtensions mapping.
|
||||
- `runId/taskId` must query `api.runtime.tasks.runs.bindSession({ sessionKey }).resolve/get/list/findLatest`.
|
||||
- If no native task exists, return `no_native_task_record`; do not infer success from artifacts.
|
||||
- If `includeArtifacts`, call export/list resolver and include artifact dir status plus expected dirs.
|
||||
|
||||
**Step 3: Run plugin checks**
|
||||
|
||||
```bash
|
||||
pnpm test src/taskState.test.ts src/exportArtifacts.test.ts
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 4: Bridge Sends Typed Metadata and Stops Session String Derivation
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/orchestrator.go`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/rpc_handler.go`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/openclaw_thread_session_mapper.go`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/types.go`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/web_contract_test.go`
|
||||
|
||||
**Step 1: Write failing bridge tests**
|
||||
|
||||
Extend the case from `docs/cases/openclaw-gateway-e2e-regression/README.md`:
|
||||
|
||||
- five concurrent OpenClaw requests remain running/completed, not queued;
|
||||
- Bridge calls `xworkmate.session.prepare` before `chat.send`;
|
||||
- request to plugin includes `appThreadKey`, `openclawSessionKey`, `expectedArtifactDirs`;
|
||||
- no code path calls `ThreadSessionMapper.OpenClawSessionID` for the new path;
|
||||
- events include `appThreadKey`, `openclawSessionKey`, `runId/taskId` where available.
|
||||
|
||||
**Step 2: Run failing bridge test**
|
||||
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge
|
||||
go test ./internal/acp -run TestHTTPHandlerGatewayOpenClawHandlesFiveConcurrentE2ECases -count=1
|
||||
```
|
||||
|
||||
Expected: FAIL until prepare call and metadata are wired.
|
||||
|
||||
**Step 3: Implement Bridge changes**
|
||||
|
||||
In `startOpenClawGatewayTask`:
|
||||
|
||||
- validate and normalize `expectedArtifactDirs`;
|
||||
- compute or receive the actual OpenClaw session key from the Bridge request path, but do not use legacy string replacement;
|
||||
- call plugin `xworkmate.session.prepare` before `chat.send`;
|
||||
- pass `expectedArtifactDirs` to artifact export/list as explicit typed params;
|
||||
- treat plugin mapping conflicts as fail-closed.
|
||||
|
||||
In `xworkmate.tasks.get` handler:
|
||||
|
||||
- forward lookup to plugin/native path;
|
||||
- stop reconstructing `OpenClawTaskRecord` from `artifactScope` as a source of truth;
|
||||
- use native reconciliation on reconnect.
|
||||
|
||||
**Step 4: Run bridge checks**
|
||||
|
||||
```bash
|
||||
go test ./internal/acp -count=1
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go build ./...
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 5: App Typed Lookup and Recovery Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/runtime/runtime_models_runtime_payloads.dart`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/runtime/go_task_service_client.dart`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/runtime/external_code_agent_acp_desktop_transport.dart`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_actions.dart`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/test/runtime/assistant_execution_target_test.dart`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/test/runtime/gateway_acp_client_auth_test.dart`
|
||||
- Test: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/test/runtime/desktop_thread_artifact_service_test.dart`
|
||||
|
||||
**Step 1: Write failing App tests**
|
||||
|
||||
Use `docs/cases/openclaw-gateway-e2e-regression/README.md` as the fixture source:
|
||||
|
||||
- five canonical prompts submit with `appThreadKey` and `expectedArtifactDirs`;
|
||||
- `xworkmate.tasks.get` params include `appThreadKey`, `openclawSessionKey` when known, `runId`, and include flags;
|
||||
- `no_native_task_record` is rendered as an explicit recovery state, not mistaken for success;
|
||||
- artifact sidebar keeps no-artifact empty state and blocks stale files.
|
||||
|
||||
**Step 2: Run failing tests**
|
||||
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app
|
||||
flutter test test/runtime/assistant_execution_target_test.dart
|
||||
flutter test test/runtime/gateway_acp_client_auth_test.dart
|
||||
flutter test test/runtime/desktop_thread_artifact_service_test.dart
|
||||
```
|
||||
|
||||
Expected: FAIL until typed lookup fields are added.
|
||||
|
||||
**Step 3: Implement App changes**
|
||||
|
||||
- Add `appThreadKey`, `openclawSessionKey`, and `expectedArtifactDirs` to task association / task-get DTOs.
|
||||
- Keep current metadata generation, but rename/extend the contract so `appThreadKey` is explicit.
|
||||
- Preserve directories even if not present locally.
|
||||
- Do not read or persist secrets differently.
|
||||
|
||||
**Step 4: Run targeted App checks**
|
||||
|
||||
```bash
|
||||
flutter analyze
|
||||
flutter test test/runtime/assistant_execution_target_test.dart
|
||||
flutter test test/runtime/gateway_acp_client_auth_test.dart
|
||||
flutter test test/runtime/desktop_thread_artifact_service_test.dart
|
||||
flutter test test/features/assistant/assistant_artifact_sidebar_test.dart
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 6: Remove/Downgrade Custom State Centers
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins/src/taskState.ts`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/openclaw_async_tasks.go`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/types.go`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/internal/acp/rpc_handler.go`
|
||||
|
||||
**Step 1: Delete dead primary state paths**
|
||||
|
||||
Remove or downgrade:
|
||||
|
||||
- plugin in-memory task/session maps as truth;
|
||||
- Bridge `OpenClawTaskRecord` terminal authority;
|
||||
- Bridge task reassociation from `artifactScope`;
|
||||
- session key string derivation as primary route.
|
||||
|
||||
Retain only transport/reconciliation caches that have clear owner, scope, and invalidation rules.
|
||||
|
||||
**Step 2: Static guard**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "agent:main:\\$|replace\\(|ThreadSessionMapper|createXWorkmateTaskStore|sessionMappingsBy|records = new Map|bridgeAgents" \
|
||||
/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins \
|
||||
/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge \
|
||||
/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app
|
||||
```
|
||||
|
||||
Expected: only documented migration or tests remain.
|
||||
|
||||
## Task 7: Docs and Case Updates
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/ai-context/chain-map.md`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/chain-map-task-execution.md`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/chain-map-artifact-lifecycle.md`
|
||||
- Modify: `/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/cases/openclaw-gateway-e2e-regression/README.md`
|
||||
|
||||
**Step 1: Update source-of-truth docs**
|
||||
|
||||
Document:
|
||||
|
||||
- mapping stored in `SessionEntry.pluginExtensions`;
|
||||
- Bridge pre-chat `xworkmate.session.prepare`;
|
||||
- expected dirs typed propagation;
|
||||
- native task-registry terminal state;
|
||||
- App SSE as transport only.
|
||||
|
||||
**Step 2: Update case acceptance**
|
||||
|
||||
Add explicit checks to the OpenClaw E2E case:
|
||||
|
||||
- pluginExtensions contains `draft:*` <-> `agent:main:draft:*`;
|
||||
- `expectedArtifactDirs` visible in plugin task output;
|
||||
- `xworkmate.tasks.get` returns native task or `no_native_task_record`;
|
||||
- no stale artifact display.
|
||||
|
||||
## Task 8: Full Regression and Manual Verification
|
||||
|
||||
**Automated commands:**
|
||||
|
||||
```bash
|
||||
# openclaw-multi-session-plugins
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-multi-session-plugins
|
||||
pnpm test
|
||||
pnpm typecheck
|
||||
pnpm pack:check
|
||||
|
||||
# xworkmate-bridge
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go build ./...
|
||||
|
||||
# xworkmate-app
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app
|
||||
flutter analyze
|
||||
flutter test test/runtime/assistant_execution_target_test.dart
|
||||
flutter test test/runtime/gateway_acp_client_auth_test.dart
|
||||
flutter test test/runtime/desktop_thread_artifact_service_test.dart
|
||||
flutter test test/features/assistant/assistant_artifact_sidebar_test.dart
|
||||
flutter test
|
||||
```
|
||||
|
||||
**Manual cases from `docs/cases/`:**
|
||||
|
||||
- `OPENCLAW-E2E-001` through `OPENCLAW-E2E-005` from `docs/cases/openclaw-gateway-e2e-regression/README.md`.
|
||||
- `MANUAL-GATEWAY-001` through `MANUAL-GATEWAY-004` from `docs/cases/core-integration-manual-cases.md`.
|
||||
- `MANUAL-THREAD-002` for cross-thread state/artifact isolation.
|
||||
|
||||
**Acceptance evidence to record:**
|
||||
|
||||
- App thread path: `$HOME/.xworkmate/threads/draft-1780636411666238-3`.
|
||||
- `appThreadKey`: `draft:1780636411666238-3`.
|
||||
- `openclawSessionKey`: `agent:main:draft:1780636411666238-3`.
|
||||
- Plugin mapping exists in `SessionEntry.pluginExtensions`.
|
||||
- `expectedArtifactDirs` survives App -> Bridge -> Plugin -> artifact resolver.
|
||||
- No Bridge primary path uses string replace/prefix derivation.
|
||||
- Plugin has no primary custom task/session/event store.
|
||||
|
||||
## Rollback Point
|
||||
|
||||
Keep the rollback boundary after Task 4:
|
||||
|
||||
- If Plugin mapping works but Bridge cannot safely pre-call `xworkmate.session.prepare`, stop before App rollout.
|
||||
- Revert Bridge/App protocol changes while retaining plugin tests and docs.
|
||||
- Do not introduce OpenClaw upstream patch as a workaround.
|
||||
|
||||
## Residual Risks
|
||||
|
||||
- If ordinary OpenClaw `chat.send` does not create native task records, `xworkmate.tasks.get` must return `no_native_task_record` until Bridge uses an existing native task-producing OpenClaw path or a native registry mirror API exposed by the read-only upstream.
|
||||
- Because upstream `session_start` has no typed xworkmate metadata, `source: "session_start"` cannot be the main new-path source without upstream changes. In the no-upstream-change plan, use `source: "bridge_prepare"` for current new writes and reserve `session_start` only for future upstream support.
|
||||
- The plugin package may need dependency alignment with OpenClaw 2026.6.1 types before `pnpm typecheck` is stable.
|
||||
Loading…
Reference in New Issue
Block a user