Create instance workloads as single-replica Deployments instead of bare Pods so Kubernetes can recreate Pods after deletion, eviction, or node/runtime failures.
Update stop/delete and orphan cleanup to remove Deployments before residual Pods, and improve status sync to keep instances creating while a Deployment exists without a Pod and avoid stale Failed/Evicted Pods affecting instance state.
- Disable Webtop/KasmVNC bidirectional clipboard sync by default to reduce accidental paste during remote desktop input
- Derive desktop runtime /dev/shm defaults from instance memory: 1G for 4G, 2G for 8G, and 4G for 12G+
- Preserve explicit SHM_SIZE_GB overrides and keep shell runtime behavior unchanged
- Add tests for openclaw/hermes clipboard policy and /dev/shm default selection
- Support multiple OpenClaw channel resources and fix alias injection
Add cursor-based GET /teams/:id/tasks and GET /teams/:id/events APIs.
Team detail page loads older messages on scroll/click and deduplicates
collaboration chat messages with improved thread ordering.
- Add migrations plus Team/Member/Task/Event APIs; Redis Streams consumer projects inbox/events into DB as source of truth
- Inject Team Secret (Redis URL, team token) via envFrom; shared RWX PVC at /team; sync ConfigMap roster to /team/team.json
- Create member Pods through InstanceService; extend K8s (PVC/Secret/Pod/ConfigMap); stale-task sweep and Team/member deletion with cleanup
- Add /teams, /teams/new, /teams/🆔 creation wizard (roster, presets, shared env/OpenClaw plan), per-member desktops, collaboration timeline, debug dispatch (defaults to Leader when target omitted)
- add configurable /dev/shm mounts for instance pods with a bounded SHM_SIZE_GB override
- introduce pod security modes and use chromium-compat for OpenClaw instead of privileged
- keep privileged mode only as an explicit admin fallback
- remove the node-level clawmanager-node-tuner manifest to avoid changing host security defaults
- fix k3s HTTPS and API/proxy service port mappings
- add tests for SHM parsing and pod security mode behavior
BREAKING: PV hostPath prefix changed from /tmp/clawreef/ to /data/clawreef/
Problem:
- PV hostPath was hardcoded to /tmp/clawreef/user-{id}/instance-{id}
- /tmp/ is a volatile directory that may be cleaned by systemd-tmpfiles
or cleared on node reboot (depending on OS configuration)
- This caused data loss when worker-02 was rebooted by the hypervisor
Fix:
- Add configurable hostPathPrefix to RuntimePVCConfig (default: /data/clawreef)
- Support K8S_PV_HOST_PATH_PREFIX environment variable override
- Update deployment manifests to use /data/clawmanager/ for MySQL and MinIO
Migration:
- Existing deployments should move data from /tmp/clawreef/ to /data/clawreef/
and create a symlink for backward compatibility:
mkdir -p /data/clawreef
mv /tmp/clawreef/* /data/clawreef/
rm -rf /tmp/clawreef && ln -s /data/clawreef /tmp/clawreef
TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields and
TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts were
declared twice causing build failure.
Every channel form editor (telegram, dingtalk-connector, slack, feishu)
silently drops fields on Save. The user flow that loses data:
1. Open a channel resource in the JSON tab, add any field not owned
by the form (e.g. webhook, custom capabilities, additional feishu
accounts).
2. Switch to the Form tab and change anything (e.g. rotate a secret).
3. Click Save.
4. The field from step 1 is gone from the saved config — and stays
gone after a reload.
The data loss occurs in two places; both must be fixed:
Frontend (OpenClawConfigCenterPage.tsx):
Each update*ChannelContentText rebuilt the output from a hard-coded
allowlist instead of merging form fields into the parsed config. Any
field not in the allowlist was dropped on Save. For feishu this
collapsed a multi-account config to a single `main` account.
Backend (openclaw_config_service.go):
normalizeOpenClawResourceContent calls normalize*ChannelConfigForEnv
on every create/update to validate and rebuild the stored config.
Those helpers use the same allowlist pattern, so even if the
frontend sends a complete config, the backend strips tenant-authored
keys before persisting.
Fix:
- Frontend: introduce a single mergeChannelConfig(existing, allowlisted)
helper and route every editor's output through it. Each handler
builds an allowlisted object containing only its known keys; the
helper overlays it onto the parsed existing config so unknown keys
survive. The merge point is now structural rather than a per-handler
convention, so a new editor (e.g. an MS Teams handler added in a
downstream patch) can't regress by forgetting the spread. Feishu's
accounts deep-merge is preserved by building the merged accounts
inside the allowlisted object before handing off to the helper.
- Backend: introduce mergeOpenClawChannelConfigForStorage, which
overlays the allowlist-normalized output onto the original parsed
payload at storage time. Normalized keys still win; unknown keys
pass through. For feishu the accounts map is merged member-wise.
Env-rendering semantics are unchanged. appendCompiledOpenClawEnvPayload
continues to call the *ForEnv helpers at render time against the
stored content, so the runtime env payload sees the same allowlist as
before.
Tests:
- Two existing ResourcePayloadFromModelNormalizesStored*JSON tests
asserted the pre-fix behavior (dropping `legacyField`) and are
updated to assert it survives.
- New TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields
(table-driven, three editors) guards the preservation invariant.
- New TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts
guards the feishu deep-merge.
Manual verification against a live instance: add `webhook` via the
JSON tab, switch to Form tab, rotate a secret, Save, reload — the
webhook field is retained.
Kubernetes defaults imagePullPolicy to Always when the image tag is
:latest. This causes pod creation failures in air-gapped and enterprise
environments where nodes cannot reach external registries.
Changes:
- Add ImagePullPolicy field to PodConfig struct
- Default to IfNotPresent in CreatePod when not explicitly set
- Support IMAGE_PULL_POLICY env var for operator override
- Apply policy to both CreateInstance and StartInstance paths
Closes#94
Every channel form editor (telegram, dingtalk-connector, slack, feishu)
silently drops fields on Save. The user flow that loses data:
1. Open a channel resource in the JSON tab, add any field not owned
by the form (e.g. webhook, custom capabilities, additional feishu
accounts).
2. Switch to the Form tab and change anything (e.g. rotate a secret).
3. Click Save.
4. The field from step 1 is gone from the saved config — and stays
gone after a reload.
The data loss occurs in two places; both must be fixed:
Frontend (OpenClawConfigCenterPage.tsx):
Each update*ChannelContentText rebuilt the output from a hard-coded
allowlist instead of merging form fields into the parsed config. Any
field not in the allowlist was dropped on Save. For feishu this
collapsed a multi-account config to a single `main` account.
Backend (openclaw_config_service.go):
normalizeOpenClawResourceContent calls normalize*ChannelConfigForEnv
on every create/update to validate and rebuild the stored config.
Those helpers use the same allowlist pattern, so even if the
frontend sends a complete config, the backend strips tenant-authored
keys before persisting.
Fix:
- Frontend: introduce a single mergeChannelConfig(existing, allowlisted)
helper and route every editor's output through it. Each handler
builds an allowlisted object containing only its known keys; the
helper overlays it onto the parsed existing config so unknown keys
survive. The merge point is now structural rather than a per-handler
convention, so a new editor (e.g. an MS Teams handler added in a
downstream patch) can't regress by forgetting the spread. Feishu's
accounts deep-merge is preserved by building the merged accounts
inside the allowlisted object before handing off to the helper.
- Backend: introduce mergeOpenClawChannelConfigForStorage, which
overlays the allowlist-normalized output onto the original parsed
payload at storage time. Normalized keys still win; unknown keys
pass through. For feishu the accounts map is merged member-wise.
Env-rendering semantics are unchanged. appendCompiledOpenClawEnvPayload
continues to call the *ForEnv helpers at render time against the
stored content, so the runtime env payload sees the same allowlist as
before.
Tests:
- Two existing ResourcePayloadFromModelNormalizesStored*JSON tests
asserted the pre-fix behavior (dropping `legacyField`) and are
updated to assert it survives.
- New TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields
(table-driven, three editors) guards the preservation invariant.
- New TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts
guards the feishu deep-merge.
Manual verification against a live instance: add `webhook` via the
JSON tab, switch to Form tab, rotate a secret, Save, reload — the
webhook field is retained.