{
  "schemaVersion": "1.0",
  "item": {
    "slug": "safe-update-merge",
    "name": "Safe Update/Merge Skill",
    "source": "tencent",
    "type": "skill",
    "category": "效率提升",
    "sourceUrl": "https://clawhub.ai/maverick-software/safe-update-merge",
    "canonicalUrl": "https://clawhub.ai/maverick-software/safe-update-merge",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/safe-update-merge",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=safe-update-merge",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "MERGE_MANIFEST.json",
      "SKILL.md",
      "references/app-state-patch.ts",
      "references/bg-sessions-backend.ts",
      "references/bg-sessions-controller.ts",
      "references/bg-sessions-views.ts"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/safe-update-merge"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/safe-update-merge",
    "agentPageUrl": "https://openagent3.xyz/skills/safe-update-merge/agent",
    "manifestUrl": "https://openagent3.xyz/skills/safe-update-merge/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/safe-update-merge/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Safe Merge Update",
        "body": "Merges upstream OpenClaw changes into your fork while preserving all custom code:\nplugin registrations, custom UI tabs, workspace skills, controllers, and state extensions."
      },
      {
        "title": "⚠️ Operator Warnings — Read Before Running",
        "body": "This skill performs high-impact, partially irreversible operations. Understand what it does before executing in a production environment:\n\nOperationPhaseImpactgit merge upstream/mainPhase 1Creates safe-merge-YYYY-MM-DD branch and merges — no changes to local-desktop-mainnpm run build / pnpm run buildPhase 1Downloads packages if lockfile changed (network); builds in placesystemctl --user restart openclaw-gatewayPhase 1Restarts the live gateway — brief downtime; gateway now runs from safe-merge buildConflict resolution via claude CLIPhase 1 (optional)Passes redacted file content to the Claude API; Edit+Read tools only — Bash explicitly excludedgit push --force to TARGET_REMOTE/TARGET_BRANCHPhase 2 only (--promote)Overwrites remote branch — only runs after user confirms gateway is healthygit branch -D safe-merge-*Phase 2 only (--promote)Deletes temp branch after successful promotion"
      },
      {
        "title": "Prerequisites",
        "body": "Before running, verify:\n\ngit remote -v shows upstream pointing to github.com/openclaw/openclaw (or set UPSTREAM_REMOTE)\ngit remote -v shows myfork pointing to your fork (or set TARGET_REMOTE)\nBranch local-desktop-main exists locally (or set TARGET_BRANCH)\nclaude CLI is installed and authenticated (claude --version)\nnpm or pnpm is available for builds\nYou are OK with the gateway restarting automatically on success"
      },
      {
        "title": "Conflict Resolution & Secret Redaction",
        "body": "When merge conflicts occur, the script:\n\nRuns scripts/redact-secrets.sh on each conflicted file — replaces secrets with [REDACTED_N] placeholders, writes the redaction map to a per-file temp file in a mode-700 directory\nInvokes the claude CLI with --allowedTools Edit,Read (no Bash) to resolve conflict markers in the redacted files — the model cannot execute shell commands\nAfter claude exits, the script runs git add -A && git commit --no-edit itself\nRestores secrets from the redaction map, then deletes the map files immediately\n\nIf you prefer manual resolution, skip claude entirely: fix conflicts yourself and run --resume."
      },
      {
        "title": "Flags",
        "body": "FlagDescription--dry-runFetch remotes, show divergence and estimated conflict count, then exit — no changes made--no-auto-resolveStop on conflicts instead of invoking claude — leaves the safe-merge branch for manual resolution, then use --resume--resume <branch>Skip the merge; run build → restart on an existing safe-merge branch (use after manual conflict resolution)--promote <branch>Phase 2 — run after user confirms the gateway is healthy. Force-pushes the safe-merge branch to TARGET_REMOTE/TARGET_BRANCH, switches local branch, deletes temp branch. This is the only step that modifies local-desktop-main."
      },
      {
        "title": "All Environment Variables",
        "body": "VariableDefaultDescriptionREPO_DIR(required)Path to your OpenClaw repositoryUPSTREAM_REMOTEupstreamRemote name for openclaw/openclawUPSTREAM_BRANCHmainBranch to pull from upstreamTARGET_REMOTEmyforkRemote to force-push result toTARGET_BRANCHlocal-desktop-mainBranch to promote on successLOCAL_BRANCHcurrent branchThe branch being merged from (used by preflight.sh for divergence stats)PACKAGE_MGRauto-detectnpm or pnpm (auto-detected from lockfile)"
      },
      {
        "title": "How It Works",
        "body": "Pre-flight: Fetch upstream, compute divergence, detect conflicts\nAI Merge: The agent resolves each conflict using the merge manifest as intent context\nValidate: Build check, tab verification, protected pattern scan\nReport: Announce results back to the requesting session"
      },
      {
        "title": "Model Usage",
        "body": "This skill uses the claude CLI (the Anthropic Claude Code CLI, not the OpenClaw agent model) for conflict resolution. The CLI must be installed separately and authenticated with an Anthropic API key in your environment. It is invoked with --allowedTools Edit,Read — the Bash tool is explicitly excluded, so the model can only read and edit files, not execute shell commands.\n\nThe model sees only the redacted content of conflicted files (see Secret Redaction below) — not the entire repository.\n\nTo control which Claude model is used, configure it in your ~/.claude/config.json or set the model via claude CLI flags."
      },
      {
        "title": "Secret Redaction",
        "body": "Before any file content is sent to the claude CLI for conflict resolution, it passes through scripts/redact-secrets.sh which detects and replaces:\n\nAPI keys (OpenAI sk-, GitHub ghp_, Slack xoxb-, AWS AKIA, etc.)\nBearer/Basic auth tokens\nPrivate keys (RSA, EC, OPENSSH)\nConnection strings with embedded passwords\nConfig values for password, secret, token, apiKey fields\n\nDetected secrets are replaced with [REDACTED_N] placeholders.\n\nHow the map is stored: redact-secrets.sh requires fd 3 to be open and writes the redaction map to whatever file fd 3 points to. In safe-merge-update.sh, fd 3 is wired to a per-file temp file inside a mode-700 temp directory (mktemp -d). So the map is written to disk — in a private, process-owned temp directory — and deleted immediately after secret restoration. It is never written to stdout, stderr, or any shared/world-readable path.\n\nIf your security policy requires no disk writes, mount the temp dir on a tmpfs:\n\nREDACT_MAP_DIR=$(mktemp -d)\nmount -t tmpfs -o size=1m,mode=700 tmpfs \"$REDACT_MAP_DIR\"\n# ... run safe-merge-update.sh with REDACT_MAP_DIR set ...\numount \"$REDACT_MAP_DIR\" && rmdir \"$REDACT_MAP_DIR\"\n\nWhat is sent to the claude CLI: Only the redacted content of conflicted files. Claude resolves conflict markers (<<<<<<<, =======, >>>>>>>) using only the Edit and Read tools — Bash is not granted, so the model cannot execute shell commands. The script itself runs git add -A && git commit --no-edit after claude finishes.\n\nBackups in /tmp/safe-merge/backups/ contain only redacted content — they are created after redaction and never contain plaintext secrets."
      },
      {
        "title": "What Gets Sent to the Model",
        "body": "Only conflicting file diffs are sent (not the entire repository)\nAll secrets are redacted before transmission (see above)\nThe merge manifest describes file intents and protected patterns\n.env files are never included in merge prompts"
      },
      {
        "title": "Build Execution",
        "body": "The validation phase runs pnpm install --ignore-scripts, pnpm build, and pnpm ui:build to verify the merge compiles.\n\npnpm install will download packages from the npm registry. This is network activity. --ignore-scripts is always passed to suppress preinstall/postinstall lifecycle hooks from running untrusted code.\n\nBefore proceeding past pre-flight:\n\nReview upstream package.json diffs in the pre-flight report\nCheck for new dependencies you don't recognize before running install\nFor maximum safety, run the full merge in an isolated environment (container, VM, or disposable clone)\n\nSKILL.md says \"No Network Installs\" — this refers to the skill package itself (no curl/wget/npm install in the skill's own setup). It does NOT mean the merge workflow avoids network; git fetch upstream and pnpm install both require network access."
      },
      {
        "title": "Backups (Non-Negotiable)",
        "body": "Before ANY file edits, the skill creates a full backup of every conflicting file at /tmp/safe-merge/backups/ preserving directory structure. Backups are created after secret redaction — they never contain plaintext secrets. If the merge goes wrong, restore from backups and re-run secret restoration."
      },
      {
        "title": "No Network Installs",
        "body": "This skill contains no install scripts that download from external URLs. All files are local to the skill package. The only network activity is git fetch upstream and your normal model API calls."
      },
      {
        "title": "Via UI",
        "body": "Click the ↑ Update button in the topbar (right of Health pill)."
      },
      {
        "title": "Via Chat",
        "body": "/update — or ask: \"run a safe merge update\""
      },
      {
        "title": "Fully Automated Pipeline (scripts/safe-merge-update.sh)",
        "body": "The primary entry point. Run this directly — it handles the entire workflow end-to-end:\n\ncd /path/to/openclaw\n\n# Safe first step — shows divergence, makes no changes\nREPO_DIR=. ./scripts/safe-merge-update.sh --dry-run\n\n# Phase 1 — merge, build, restart gateway from safe-merge branch\n# local-desktop-main is NOT touched; no remote push yet\nREPO_DIR=. ./scripts/safe-merge-update.sh\n\n# Manual mode — stop on conflicts for review instead of invoking claude\nREPO_DIR=. ./scripts/safe-merge-update.sh --no-auto-resolve\n\n# Resume after fixing conflicts manually (still Phase 1)\nREPO_DIR=. ./scripts/safe-merge-update.sh --resume safe-merge-2026-03-02\n\n# Phase 2 — after verifying the gateway is healthy, promote to local-desktop-main\nREPO_DIR=. ./scripts/safe-merge-update.sh --promote safe-merge-2026-03-02\n\nTwo-phase design — local-desktop-main is never touched until the user confirms:\n\nPhase 1 (automated):\n\nChecks you're on local-desktop-main with a clean working tree\n--dry-run: fetches, shows upstream divergence + estimated conflict count, exits — no changes\ngit fetch --all\nPrunes any stale safe-merge-* branches (prevents accumulation)\nCreates safe-merge-YYYY-MM-DD branch\nRuns git merge upstream/main --no-edit\nIf conflicts → redacts secrets (maps written to mode-700 temp dir via fd 3, deleted after restore), invokes claude --allowedTools Edit,Read (Bash excluded), restores secrets, runs git commit — use --no-auto-resolve to stop for manual review\npnpm build + pnpm ui:build\nRestarts gateway from current working tree (safe-merge branch build)\nExits with confirmation prompt — local-desktop-main is unchanged, no remote push yet\nOn build failure: leaves safe-merge branch intact for investigation\n\nPhase 2 (user-confirmed via --promote):\n\nUser verifies gateway is healthy\ngit push myfork safe-merge-YYYY-MM-DD:local-desktop-main --force\ngit checkout local-desktop-main && git reset --hard safe-merge-YYYY-MM-DD\ngit branch -D safe-merge-YYYY-MM-DD\n\nRollback before confirming: gateway restart reverts to previous build; local-desktop-main was never touched.\n\nClaude conflict resolution strategy (baked into the script):\n\nlocal-desktop-main UI/vault/navigation customizations → prefer HEAD (ours)\nUpstream security and bug fixes → prefer incoming (theirs)\nAdditive changes on both sides → keep both\nTypeScript type unions → union both sides\nGenuinely ambiguous → prefer HEAD\n\nResume mode — for cases where Claude's auto-resolution needs a manual touch:\n\n# Fix conflicts manually, then:\n./scripts/safe-merge-update.sh --resume safe-merge-2026-03-02\n# Skips merge, runs build → push → cleanup"
      },
      {
        "title": "Supporting Scripts (for advanced / manual use)",
        "body": "scripts/preflight.sh — read-only analysis, produces JSON report at /tmp/safe-merge/preflight-report.json\nscripts/validate.sh — post-merge build + mustPreserve pattern checks\nscripts/merge-agent-prompt.md — prompt template for per-file conflict resolution (used when invoking Claude manually)\nscripts/redact-secrets.sh — secret detection and redaction before model transmission"
      },
      {
        "title": "Branch Strategy",
        "body": "Working branch: local-desktop-main (your fork's primary branch)\nTemp merge branch: safe-merge-YYYY-MM-DD (created and destroyed per run)\nUpstream source: upstream/main (openclaw/openclaw official repo)\nPush target: myfork/local-desktop-main\nStale safe-merge branches are auto-pruned at the start of each run — no accumulation"
      },
      {
        "title": "Phase 1: Pre-flight (scripts/preflight.sh)",
        "body": "Run automatically when invoked. Produces a report at /tmp/safe-merge/preflight-report.json.\n\nWhat it does:\n\nFetches upstream (git fetch upstream)\nComputes commit divergence (ahead/behind counts)\nLists conflicting files via git merge-tree\nChecks each conflict against the merge manifest for protection status\nCreates a temporary worktree for dry-run merge (avoids touching your working tree)\n\nPre-flight report includes:\n\nDivergence stats\nList of conflicting files with protection status\nRecommended strategy per file (keep-ours, ai-merge, accept-upstream)\n\nEnvironment variables:\n\nREPO_DIR — Path to your OpenClaw repo (must be set explicitly)\nUPSTREAM_REMOTE — Upstream remote name (default: upstream)\nUPSTREAM_BRANCH — Upstream branch (default: main)"
      },
      {
        "title": "Phase 2: Merge & Conflict Resolution",
        "body": "The agent performs the actual merge and resolves conflicts:\n\nCreate merge branch — git checkout -b safe-merge-YYYY-MM-DD\nExamine conflicts before merging — read both our version and upstream's version of each conflicting file to understand what each side changed\nRun the merge — git merge upstream/main --no-commit --no-ff\nFor each conflicting file:\na. Read the conflict markers to understand the exact diff\nb. Check MERGE_MANIFEST.json for intent and strategy (keep-ours, accept-upstream, ai-merge)\nc. Resolve: write the clean merged file preserving our customizations + upstream improvements\nd. git add the resolved file\nVerify auto-merged protected files — even files that auto-merge need checking: grep for mustPreserve patterns to confirm our custom code survived\nSecret redaction (for complex conflicts requiring prompt-based resolution):\n\nscripts/redact-secrets.sh replaces secrets with [REDACTED_N] placeholders\nRedaction map is written to a per-file temp file in a mode-700 directory via fd 3; it is never written to stdout, stderr, or any shared path, and is deleted immediately after secret restoration\nAfter resolution, restore secrets from map, then delete the temp directory\n\nKey principle: Always examine both sides of a conflict BEFORE attempting resolution. Understanding what upstream changed and what we customized is essential for correct merges."
      },
      {
        "title": "Phase 3: Validation (scripts/validate.sh)",
        "body": "After all conflicts are resolved:\n\npnpm install --ignore-scripts — install any new dependencies (lifecycle scripts suppressed)\npnpm build — compile the gateway\npnpm ui:build — compile the Control UI\nProtected pattern scan — verify mustPreserve patterns from the manifest still exist\nTab verification — check that custom UI tabs are still registered"
      },
      {
        "title": "Phase 4: Commit & Report",
        "body": "Commits on the merge branch (safe-merge-YYYY-MM-DD) — never directly on main\nCommit message includes: upstream version, commit count, conflict resolution summary, file counts\nReports to user: files resolved, strategies used, build status, any warnings\nUser decides whether to merge the branch into main and push:\ngit checkout main\ngit merge safe-merge-YYYY-MM-DD\ngit push origin main"
      },
      {
        "title": "Configuration",
        "body": "VariableDefaultDescriptionREPO_DIR(required, declared in metadata)Path to your OpenClaw repositorySAFE_MERGE_MODEL(agent's current model)Model override for conflict resolutionUPSTREAM_REMOTEupstreamGit remote name for upstreamUPSTREAM_BRANCHmainUpstream branch to merge from"
      },
      {
        "title": "Merge Model Selection",
        "body": "The model used for AI conflict resolution can be configured in two ways:\n\nVia the UI modal — The update modal includes a \"Merge Model\" dropdown that lists all available models (same catalog as Agents → Model Selection). The selection is persisted in localStorage under the key openclaw-merge-model and survives page reloads. When a model is selected and \"Run Safe Merge\" is clicked, the merge prompt includes SAFE_MERGE_MODEL=<selected-model>.\n\n\nVia environment variable — Set SAFE_MERGE_MODEL before invoking the skill (e.g., in the agent's env config or inline).\n\nIf neither is set, the skill uses the agent's currently configured primary model.\n\nThe dropdown appears on both the initial \"Check for Updates\" screen and the results screen, so you can change it at any point before starting the merge."
      },
      {
        "title": "Files",
        "body": "FilePurposeSKILL.mdThis file — skill instructionsMERGE_MANIFEST.jsonProtected files, intents, mustPreserve patternsscripts/preflight.shPre-flight analysis (read-only, no modifications)scripts/validate.shPost-merge build and pattern validationscripts/merge-agent-prompt.mdPrompt template for per-file conflict resolutionscripts/redact-secrets.shSecret detection and redaction before model transmissionupdate-modal.tsReference copy of the UI update modal component (source of truth: ui/src/ui/views/update-modal.ts)references/bg-sessions-backend.tsReference: src/gateway/server-methods/bg-sessions.tsreferences/bg-sessions-controller.tsReference: ui/src/ui/controllers/bg-sessions.tsreferences/bg-sessions-views.tsReference: ui/src/ui/views/bg-sessions.ts"
      },
      {
        "title": "Background Sessions Panel",
        "body": "A right-side drawer panel that lets you watch and talk to background/cron subagents in real-time, without leaving the Control UI."
      },
      {
        "title": "How to Open",
        "body": "Click the updates badge (e.g. \"148 Updates\") in the status bar — opens the update modal and slides in the background sessions panel simultaneously\nCan also be opened programmatically: openBgSessionsPanel(client, state)"
      },
      {
        "title": "What It Shows",
        "body": "Session list dropdown — all isolated/cron sessions, with 🟢 running / ⚪ idle indicator and a human-readable label extracted from the cron job name\nLive transcript — scrollable, role-colored messages:\n\n🟣 You (user injections)\n🟢 Agent (assistant responses)\n🟡 → tool (tool calls with args)\n⚪ ← result (tool results, truncated)\n🔴 system (compaction events)\n\n\nAuto-refresh — polls bgSessions.history every 3 seconds while panel is open\nSend messages — text area at the bottom; Enter sends, Shift+Enter newlines. Routes through chat.send RPC using the selected sessionKey"
      },
      {
        "title": "Architecture",
        "body": "Backend (src/gateway/server-methods/bg-sessions.ts):\n\nbgSessions.list — reads sessions.json for the default agent, filters for isolated/cron session keys (UUID format: agent:main:<uuid>), returns label, updatedAt, running status (lock file presence)\nbgSessions.history — loads the .jsonl transcript file via readSessionMessages(), simplifies to { role, text, timestamp, toolName } array\nbgSessions.send is intentionally omitted — the UI calls chat.send directly with the target sessionKey\n\nController (ui/src/ui/controllers/bg-sessions.ts):\n\nopenBgSessionsPanel(client, state) — sets panel open, fetches sessions, starts 3s poll\ncloseBgSessionsPanel(state) — clears panel open flag, stops poll\nloadBgSessions(client, state) — fetches session list via RPC\nloadBgSessionHistory(client, state, key) — fetches transcript via RPC\nselectBgSession(client, state, key) — changes selected session, reloads history\nsendBgMessage(client, state) — calls chat.send RPC with session key + message, refreshes history\nstartBgSessionsPolling(client, state) / stopBgSessionsPolling() — manage setInterval\n\nView (ui/src/ui/views/bg-sessions.ts):\n\nrenderBgSessionsPanel(state, client) — full Lit HTML panel, rendered as overlay in app-render.ts\nbgSessionsPanelStyles — exported CSS (for reference; styles are embedded in the view's html template)\nPanel is an overlay div (fixed inset-0, z-index 9000) with a right-anchored 520px panel; click outside to close\n\nState fields (added to AppViewState and OpenClawApp):\n\nbgSessionsPanelOpen: boolean;\nbgSessionsList: BgSession[] | null;\nbgSessionsLoading: boolean;\nbgSessionsSelectedKey: string | null;\nbgSessionsHistory: BgMessage[] | null;\nbgSessionsHistoryLoading: boolean;\nbgSessionsInput: string;\nbgSessionsSending: boolean;"
      },
      {
        "title": "Wiring in app-render.ts",
        "body": "The panel is rendered as the last overlay before the closing </div>:\n\n${state.bgSessionsPanelOpen && state.client ? renderBgSessionsPanel(state, state.client) : nothing}\n\nThe update badge click handler also calls openBgSessionsPanel:\n\n@click=${() => {\n  // ...existing update modal logic...\n  if ((state as any).client) { openBgSessionsPanel((state as any).client, state as any); }\n}}"
      },
      {
        "title": "MERGE_MANIFEST.json — Add These Entries",
        "body": "When merging future upstream changes, protect these new files:\n\n\"src/gateway/server-methods/bg-sessions.ts\": {\n  \"intent\": \"New RPC handlers for background session listing and history (bgSessions.list, bgSessions.history)\",\n  \"strategy\": \"keep-ours\"\n},\n\"ui/src/ui/controllers/bg-sessions.ts\": {\n  \"intent\": \"Controller for the background sessions panel — load, poll, send, select\",\n  \"strategy\": \"keep-ours\"\n},\n\"ui/src/ui/views/bg-sessions.ts\": {\n  \"intent\": \"Right-side drawer panel for viewing/talking to cron subagents\",\n  \"strategy\": \"keep-ours\"\n}\n\nAlso add these mustPreserve patterns to the relevant existing protected files:\n\nui/src/ui/app-render.ts: renderBgSessionsPanel, openBgSessionsPanel, bgSessionsPanelOpen\nui/src/ui/app.ts: bgSessionsPanelOpen, bgSessionsList, bgSessionsSelectedKey\nui/src/ui/app-view-state.ts: bgSessionsPanelOpen, bgSessionsList"
      },
      {
        "title": "UI Update Modal",
        "body": "Clicking the topbar update button (in any state) opens an update modal with a guided flow:\n\nCheck for Updates — asks the user to confirm, then calls update.checkUpstream RPC with force: true (bypasses cache)\nStatus Result — shows upstream divergence: commits behind, commits ahead, or \"Up to date\"\nAction Buttons — \"⚡ Run Safe Merge\" (if behind) or \"🔄 Run Merge Anyway\" (if up to date) — both send the merge prompt to the chat session\n\nThe modal is rendered by ui/src/ui/views/update-modal.ts and uses state properties updateModalState (closed/confirm/checking/result), upstreamDivergence, and mergeModel on the app component. A reference copy of the modal source is kept at skills/safe-merge-update/update-modal.ts."
      },
      {
        "title": "Button States",
        "body": "N Updates (accent-colored): Git upstream has N newer commits\nUpdates Available (accent-colored): npm registry has a newer version (non-fork workflows)\n✓ Up to Date (muted pill, clickable): Up to date — click still opens the modal to re-check\nMerging… (spinner, disabled): Merge in progress"
      },
      {
        "title": "How Update Detection Works",
        "body": "The gateway runs two parallel checks:\n\nnpm registry (update-startup.ts): Compares VERSION against npm latest. Used for standard installs.\nGit upstream (update.checkUpstream RPC): Runs git fetch upstream && git rev-list --count HEAD..upstream/main. Used for fork workflows. Result is cached for 5 minutes.\n\nFor forks, the git check is authoritative — your local package.json version will often be ahead of npm (since you're building from source), so the npm check would incorrectly say \"up to date.\""
      },
      {
        "title": "Post-Merge Checklist",
        "body": "After a successful merge, always:\n\nRun pnpm ui:build — the Control UI is served from dist/control-ui/\nUpdate systemd service version — the UI header reads OPENCLAW_SERVICE_VERSION from the service unit:\nNEW_VERSION=$(node -e \"console.log(require('./package.json').version)\")\nsed -i \"s/OPENCLAW_SERVICE_VERSION=.*/OPENCLAW_SERVICE_VERSION=$NEW_VERSION/\" ~/.config/systemd/user/openclaw-gateway.service\nsystemctl --user daemon-reload\n\n\nRun openclaw gateway restart — pick up the new build\nCheck config schema compatibility — upstream may add .strict() to schemas\nClean up backups: rm -rf /tmp/safe-merge/ — while backups are redacted, remove them when no longer needed"
      },
      {
        "title": "2026-03-02 — Fully Automated Pipeline",
        "body": "Added scripts/safe-merge-update.sh — end-to-end automated merge pipeline\nSource changed from origin/main → upstream/main (now pulls real OpenClaw updates)\nFork branch updated to local-desktop-main (was main)\nConflicts auto-resolved by Claude using baked-in strategy (no human needed for happy path)\nAuto-prunes stale safe-merge-* branches on each run (prevents infinite accumulation)\nOn success: pushes to myfork/local-desktop-main, deletes temp branch, restarts gateway\nOn build failure: leaves safe-merge branch intact, exits cleanly for investigation\n--resume flag: skip merge, jump straight to build → push → cleanup (for post-manual-fix)\nMERGE_MANIFEST.json v1.2.0: added hostinger.ts, plugins-ui.ts, memory.ts as protected files\nMemory tab now shows spinner on first load (no more flash of empty content)"
      },
      {
        "title": "2026-03-01 — Background Sessions Panel",
        "body": "Added bgSessions.list and bgSessions.history RPC handlers\nAdded Background Sessions Panel UI (right-side drawer, session selector, live transcript, send input)\nClicking the updates badge now opens the panel alongside the update modal\nState fields: bgSessionsPanelOpen, bgSessionsList, bgSessionsSelectedKey, bgSessionsHistory, bgSessionsInput, bgSessionsSending, bgSessionsHistoryLoading, bgSessionsLoading\nMERGE_MANIFEST.json updated with 3 new protected files and mustPreserve patterns"
      },
      {
        "title": "2026-03-01 — Merge of 148 upstream commits",
        "body": "Conflicts resolved in: app-render.helpers.ts, app-render.ts, app-view-state.ts, app.ts. Key resolutions:\n\napp-render.helpers.ts: kept renderContextGauge, merged hideCron + sessionsHideCron from upstream, merged countHiddenCronSessions, preserved renderRecentArchivedOptions\napp-render.ts: kept our nav imports (getDynamicTabGroups, Jarvis/mode/usage); added upstream's resolveConfiguredCronModelSuggestions\napp-view-state.ts: kept sessionsAgentFilter + added sessionsHideCron from upstream\napp.ts: kept session history fields + added sessionsHideCron = true default from upstream"
      },
      {
        "title": "Discord Voice Schema (2026-02-27)",
        "body": "Upstream added .strict() to DiscordVoiceSchema, rejecting keys our fork previously supported. Fix: remove unsupported keys from config, or add them back to the schema."
      },
      {
        "title": "Control UI Assets Missing (2026-02-27)",
        "body": "pnpm build builds the gateway but NOT the Control UI. UI needs separate pnpm ui:build. The validate script now includes this."
      },
      {
        "title": "Duplicate Schema Properties (2026-02-27)",
        "body": "Git auto-merge kept both our extracted const AND upstream's inline block. Fix: manual dedup during AI conflict resolution."
      },
      {
        "title": "CSP connect-src Extensions (2026-03-01)",
        "body": "Our fork adds http://localhost:* (Jarvis voice agent) and https://api.openai.com (Realtime API) to the CSP connect-src directive in control-ui-csp.ts. Upstream only has ws: wss:. On merge, always keep our extensions — they're required for voice features."
      },
      {
        "title": "Branch Naming Convention (2026-03-01)",
        "body": "Merge branches should be date-stamped: safe-merge-YYYY-MM-DD. This makes it easy to identify merge attempts and clean up old branches."
      },
      {
        "title": "Auto-Merged Protected Files Need Verification (2026-03-01)",
        "body": "Even files that auto-merge without conflicts can lose custom code if upstream refactors the surrounding context. After merge, always verify mustPreserve patterns exist in auto-merged protected files — don't just trust git's auto-merge."
      },
      {
        "title": "pnpm install May Add Packages (2026-03-01)",
        "body": "Upstream may add new dependencies. When 162 commits are merged, pnpm install downloaded 51 new packages. This is expected but worth noting in the merge report."
      },
      {
        "title": "Safety Summary",
        "body": "✅ Backups before any edits — /tmp/safe-merge/backups/\n✅ New branch — never merges directly into current branch\n✅ Validation must pass before committing\n✅ No external downloads in skill setup — skill files are all local; merge workflow does use network for git fetch and pnpm install\n✅ No credential requirements — uses your existing agent model\n✅ Secrets redacted before model transmission — API keys, tokens, passwords, private keys\n✅ Only conflict diffs sent to model — not the entire repo\n✅ User controls merge — agent reports results, user decides to push\n✅ Dry-run in worktree — pre-flight uses a temp worktree, not your working tree\n✅ Stops on failure — if validation fails, reports and stops rather than pushing broken code"
      }
    ],
    "body": "Safe Merge Update\n\nMerges upstream OpenClaw changes into your fork while preserving all custom code: plugin registrations, custom UI tabs, workspace skills, controllers, and state extensions.\n\n⚠️ Operator Warnings — Read Before Running\n\nThis skill performs high-impact, partially irreversible operations. Understand what it does before executing in a production environment:\n\nOperation\tPhase\tImpact\ngit merge upstream/main\tPhase 1\tCreates safe-merge-YYYY-MM-DD branch and merges — no changes to local-desktop-main\nnpm run build / pnpm run build\tPhase 1\tDownloads packages if lockfile changed (network); builds in place\nsystemctl --user restart openclaw-gateway\tPhase 1\tRestarts the live gateway — brief downtime; gateway now runs from safe-merge build\nConflict resolution via claude CLI\tPhase 1 (optional)\tPasses redacted file content to the Claude API; Edit+Read tools only — Bash explicitly excluded\ngit push --force to TARGET_REMOTE/TARGET_BRANCH\tPhase 2 only (--promote)\tOverwrites remote branch — only runs after user confirms gateway is healthy\ngit branch -D safe-merge-*\tPhase 2 only (--promote)\tDeletes temp branch after successful promotion\nPrerequisites\n\nBefore running, verify:\n\ngit remote -v shows upstream pointing to github.com/openclaw/openclaw (or set UPSTREAM_REMOTE)\ngit remote -v shows myfork pointing to your fork (or set TARGET_REMOTE)\nBranch local-desktop-main exists locally (or set TARGET_BRANCH)\nclaude CLI is installed and authenticated (claude --version)\nnpm or pnpm is available for builds\nYou are OK with the gateway restarting automatically on success\nConflict Resolution & Secret Redaction\n\nWhen merge conflicts occur, the script:\n\nRuns scripts/redact-secrets.sh on each conflicted file — replaces secrets with [REDACTED_N] placeholders, writes the redaction map to a per-file temp file in a mode-700 directory\nInvokes the claude CLI with --allowedTools Edit,Read (no Bash) to resolve conflict markers in the redacted files — the model cannot execute shell commands\nAfter claude exits, the script runs git add -A && git commit --no-edit itself\nRestores secrets from the redaction map, then deletes the map files immediately\n\nIf you prefer manual resolution, skip claude entirely: fix conflicts yourself and run --resume.\n\nFlags\nFlag\tDescription\n--dry-run\tFetch remotes, show divergence and estimated conflict count, then exit — no changes made\n--no-auto-resolve\tStop on conflicts instead of invoking claude — leaves the safe-merge branch for manual resolution, then use --resume\n--resume <branch>\tSkip the merge; run build → restart on an existing safe-merge branch (use after manual conflict resolution)\n--promote <branch>\tPhase 2 — run after user confirms the gateway is healthy. Force-pushes the safe-merge branch to TARGET_REMOTE/TARGET_BRANCH, switches local branch, deletes temp branch. This is the only step that modifies local-desktop-main.\nAll Environment Variables\nVariable\tDefault\tDescription\nREPO_DIR\t(required)\tPath to your OpenClaw repository\nUPSTREAM_REMOTE\tupstream\tRemote name for openclaw/openclaw\nUPSTREAM_BRANCH\tmain\tBranch to pull from upstream\nTARGET_REMOTE\tmyfork\tRemote to force-push result to\nTARGET_BRANCH\tlocal-desktop-main\tBranch to promote on success\nLOCAL_BRANCH\tcurrent branch\tThe branch being merged from (used by preflight.sh for divergence stats)\nPACKAGE_MGR\tauto-detect\tnpm or pnpm (auto-detected from lockfile)\nHow It Works\nPre-flight: Fetch upstream, compute divergence, detect conflicts\nAI Merge: The agent resolves each conflict using the merge manifest as intent context\nValidate: Build check, tab verification, protected pattern scan\nReport: Announce results back to the requesting session\nSecurity & Privacy\nModel Usage\n\nThis skill uses the claude CLI (the Anthropic Claude Code CLI, not the OpenClaw agent model) for conflict resolution. The CLI must be installed separately and authenticated with an Anthropic API key in your environment. It is invoked with --allowedTools Edit,Read — the Bash tool is explicitly excluded, so the model can only read and edit files, not execute shell commands.\n\nThe model sees only the redacted content of conflicted files (see Secret Redaction below) — not the entire repository.\n\nTo control which Claude model is used, configure it in your ~/.claude/config.json or set the model via claude CLI flags.\n\nSecret Redaction\n\nBefore any file content is sent to the claude CLI for conflict resolution, it passes through scripts/redact-secrets.sh which detects and replaces:\n\nAPI keys (OpenAI sk-, GitHub ghp_, Slack xoxb-, AWS AKIA, etc.)\nBearer/Basic auth tokens\nPrivate keys (RSA, EC, OPENSSH)\nConnection strings with embedded passwords\nConfig values for password, secret, token, apiKey fields\n\nDetected secrets are replaced with [REDACTED_N] placeholders.\n\nHow the map is stored: redact-secrets.sh requires fd 3 to be open and writes the redaction map to whatever file fd 3 points to. In safe-merge-update.sh, fd 3 is wired to a per-file temp file inside a mode-700 temp directory (mktemp -d). So the map is written to disk — in a private, process-owned temp directory — and deleted immediately after secret restoration. It is never written to stdout, stderr, or any shared/world-readable path.\n\nIf your security policy requires no disk writes, mount the temp dir on a tmpfs:\n\nREDACT_MAP_DIR=$(mktemp -d)\nmount -t tmpfs -o size=1m,mode=700 tmpfs \"$REDACT_MAP_DIR\"\n# ... run safe-merge-update.sh with REDACT_MAP_DIR set ...\numount \"$REDACT_MAP_DIR\" && rmdir \"$REDACT_MAP_DIR\"\n\n\nWhat is sent to the claude CLI: Only the redacted content of conflicted files. Claude resolves conflict markers (<<<<<<<, =======, >>>>>>>) using only the Edit and Read tools — Bash is not granted, so the model cannot execute shell commands. The script itself runs git add -A && git commit --no-edit after claude finishes.\n\nBackups in /tmp/safe-merge/backups/ contain only redacted content — they are created after redaction and never contain plaintext secrets.\n\nWhat Gets Sent to the Model\nOnly conflicting file diffs are sent (not the entire repository)\nAll secrets are redacted before transmission (see above)\nThe merge manifest describes file intents and protected patterns\n.env files are never included in merge prompts\nBuild Execution\n\nThe validation phase runs pnpm install --ignore-scripts, pnpm build, and pnpm ui:build to verify the merge compiles.\n\npnpm install will download packages from the npm registry. This is network activity. --ignore-scripts is always passed to suppress preinstall/postinstall lifecycle hooks from running untrusted code.\n\nBefore proceeding past pre-flight:\n\nReview upstream package.json diffs in the pre-flight report\nCheck for new dependencies you don't recognize before running install\nFor maximum safety, run the full merge in an isolated environment (container, VM, or disposable clone)\n\nSKILL.md says \"No Network Installs\" — this refers to the skill package itself (no curl/wget/npm install in the skill's own setup). It does NOT mean the merge workflow avoids network; git fetch upstream and pnpm install both require network access.\n\nBackups (Non-Negotiable)\n\nBefore ANY file edits, the skill creates a full backup of every conflicting file at /tmp/safe-merge/backups/ preserving directory structure. Backups are created after secret redaction — they never contain plaintext secrets. If the merge goes wrong, restore from backups and re-run secret restoration.\n\nNo Network Installs\n\nThis skill contains no install scripts that download from external URLs. All files are local to the skill package. The only network activity is git fetch upstream and your normal model API calls.\n\nInvocation\nVia UI\n\nClick the ↑ Update button in the topbar (right of Health pill).\n\nVia Chat\n/update — or ask: \"run a safe merge update\"\n\nArchitecture\nFully Automated Pipeline (scripts/safe-merge-update.sh)\n\nThe primary entry point. Run this directly — it handles the entire workflow end-to-end:\n\ncd /path/to/openclaw\n\n# Safe first step — shows divergence, makes no changes\nREPO_DIR=. ./scripts/safe-merge-update.sh --dry-run\n\n# Phase 1 — merge, build, restart gateway from safe-merge branch\n# local-desktop-main is NOT touched; no remote push yet\nREPO_DIR=. ./scripts/safe-merge-update.sh\n\n# Manual mode — stop on conflicts for review instead of invoking claude\nREPO_DIR=. ./scripts/safe-merge-update.sh --no-auto-resolve\n\n# Resume after fixing conflicts manually (still Phase 1)\nREPO_DIR=. ./scripts/safe-merge-update.sh --resume safe-merge-2026-03-02\n\n# Phase 2 — after verifying the gateway is healthy, promote to local-desktop-main\nREPO_DIR=. ./scripts/safe-merge-update.sh --promote safe-merge-2026-03-02\n\n\nTwo-phase design — local-desktop-main is never touched until the user confirms:\n\nPhase 1 (automated):\n\nChecks you're on local-desktop-main with a clean working tree\n--dry-run: fetches, shows upstream divergence + estimated conflict count, exits — no changes\ngit fetch --all\nPrunes any stale safe-merge-* branches (prevents accumulation)\nCreates safe-merge-YYYY-MM-DD branch\nRuns git merge upstream/main --no-edit\nIf conflicts → redacts secrets (maps written to mode-700 temp dir via fd 3, deleted after restore), invokes claude --allowedTools Edit,Read (Bash excluded), restores secrets, runs git commit — use --no-auto-resolve to stop for manual review\npnpm build + pnpm ui:build\nRestarts gateway from current working tree (safe-merge branch build)\nExits with confirmation prompt — local-desktop-main is unchanged, no remote push yet\nOn build failure: leaves safe-merge branch intact for investigation\n\nPhase 2 (user-confirmed via --promote):\n\nUser verifies gateway is healthy\ngit push myfork safe-merge-YYYY-MM-DD:local-desktop-main --force\ngit checkout local-desktop-main && git reset --hard safe-merge-YYYY-MM-DD\ngit branch -D safe-merge-YYYY-MM-DD\n\nRollback before confirming: gateway restart reverts to previous build; local-desktop-main was never touched.\n\nClaude conflict resolution strategy (baked into the script):\n\nlocal-desktop-main UI/vault/navigation customizations → prefer HEAD (ours)\nUpstream security and bug fixes → prefer incoming (theirs)\nAdditive changes on both sides → keep both\nTypeScript type unions → union both sides\nGenuinely ambiguous → prefer HEAD\n\nResume mode — for cases where Claude's auto-resolution needs a manual touch:\n\n# Fix conflicts manually, then:\n./scripts/safe-merge-update.sh --resume safe-merge-2026-03-02\n# Skips merge, runs build → push → cleanup\n\nSupporting Scripts (for advanced / manual use)\nscripts/preflight.sh — read-only analysis, produces JSON report at /tmp/safe-merge/preflight-report.json\nscripts/validate.sh — post-merge build + mustPreserve pattern checks\nscripts/merge-agent-prompt.md — prompt template for per-file conflict resolution (used when invoking Claude manually)\nscripts/redact-secrets.sh — secret detection and redaction before model transmission\nBranch Strategy\nWorking branch: local-desktop-main (your fork's primary branch)\nTemp merge branch: safe-merge-YYYY-MM-DD (created and destroyed per run)\nUpstream source: upstream/main (openclaw/openclaw official repo)\nPush target: myfork/local-desktop-main\nStale safe-merge branches are auto-pruned at the start of each run — no accumulation\nPhases\nPhase 1: Pre-flight (scripts/preflight.sh)\n\nRun automatically when invoked. Produces a report at /tmp/safe-merge/preflight-report.json.\n\nWhat it does:\n\nFetches upstream (git fetch upstream)\nComputes commit divergence (ahead/behind counts)\nLists conflicting files via git merge-tree\nChecks each conflict against the merge manifest for protection status\nCreates a temporary worktree for dry-run merge (avoids touching your working tree)\n\nPre-flight report includes:\n\nDivergence stats\nList of conflicting files with protection status\nRecommended strategy per file (keep-ours, ai-merge, accept-upstream)\n\nEnvironment variables:\n\nREPO_DIR — Path to your OpenClaw repo (must be set explicitly)\nUPSTREAM_REMOTE — Upstream remote name (default: upstream)\nUPSTREAM_BRANCH — Upstream branch (default: main)\nPhase 2: Merge & Conflict Resolution\n\nThe agent performs the actual merge and resolves conflicts:\n\nCreate merge branch — git checkout -b safe-merge-YYYY-MM-DD\nExamine conflicts before merging — read both our version and upstream's version of each conflicting file to understand what each side changed\nRun the merge — git merge upstream/main --no-commit --no-ff\nFor each conflicting file: a. Read the conflict markers to understand the exact diff b. Check MERGE_MANIFEST.json for intent and strategy (keep-ours, accept-upstream, ai-merge) c. Resolve: write the clean merged file preserving our customizations + upstream improvements d. git add the resolved file\nVerify auto-merged protected files — even files that auto-merge need checking: grep for mustPreserve patterns to confirm our custom code survived\nSecret redaction (for complex conflicts requiring prompt-based resolution):\nscripts/redact-secrets.sh replaces secrets with [REDACTED_N] placeholders\nRedaction map is written to a per-file temp file in a mode-700 directory via fd 3; it is never written to stdout, stderr, or any shared path, and is deleted immediately after secret restoration\nAfter resolution, restore secrets from map, then delete the temp directory\n\nKey principle: Always examine both sides of a conflict BEFORE attempting resolution. Understanding what upstream changed and what we customized is essential for correct merges.\n\nPhase 3: Validation (scripts/validate.sh)\n\nAfter all conflicts are resolved:\n\npnpm install --ignore-scripts — install any new dependencies (lifecycle scripts suppressed)\npnpm build — compile the gateway\npnpm ui:build — compile the Control UI\nProtected pattern scan — verify mustPreserve patterns from the manifest still exist\nTab verification — check that custom UI tabs are still registered\nPhase 4: Commit & Report\nCommits on the merge branch (safe-merge-YYYY-MM-DD) — never directly on main\nCommit message includes: upstream version, commit count, conflict resolution summary, file counts\nReports to user: files resolved, strategies used, build status, any warnings\nUser decides whether to merge the branch into main and push:\ngit checkout main\ngit merge safe-merge-YYYY-MM-DD\ngit push origin main\n\nConfiguration\nVariable\tDefault\tDescription\nREPO_DIR\t(required, declared in metadata)\tPath to your OpenClaw repository\nSAFE_MERGE_MODEL\t(agent's current model)\tModel override for conflict resolution\nUPSTREAM_REMOTE\tupstream\tGit remote name for upstream\nUPSTREAM_BRANCH\tmain\tUpstream branch to merge from\nMerge Model Selection\n\nThe model used for AI conflict resolution can be configured in two ways:\n\nVia the UI modal — The update modal includes a \"Merge Model\" dropdown that lists all available models (same catalog as Agents → Model Selection). The selection is persisted in localStorage under the key openclaw-merge-model and survives page reloads. When a model is selected and \"Run Safe Merge\" is clicked, the merge prompt includes SAFE_MERGE_MODEL=<selected-model>.\n\nVia environment variable — Set SAFE_MERGE_MODEL before invoking the skill (e.g., in the agent's env config or inline).\n\nIf neither is set, the skill uses the agent's currently configured primary model.\n\nThe dropdown appears on both the initial \"Check for Updates\" screen and the results screen, so you can change it at any point before starting the merge.\n\nFiles\nFile\tPurpose\nSKILL.md\tThis file — skill instructions\nMERGE_MANIFEST.json\tProtected files, intents, mustPreserve patterns\nscripts/preflight.sh\tPre-flight analysis (read-only, no modifications)\nscripts/validate.sh\tPost-merge build and pattern validation\nscripts/merge-agent-prompt.md\tPrompt template for per-file conflict resolution\nscripts/redact-secrets.sh\tSecret detection and redaction before model transmission\nupdate-modal.ts\tReference copy of the UI update modal component (source of truth: ui/src/ui/views/update-modal.ts)\nreferences/bg-sessions-backend.ts\tReference: src/gateway/server-methods/bg-sessions.ts\nreferences/bg-sessions-controller.ts\tReference: ui/src/ui/controllers/bg-sessions.ts\nreferences/bg-sessions-views.ts\tReference: ui/src/ui/views/bg-sessions.ts\nBackground Sessions Panel\n\nA right-side drawer panel that lets you watch and talk to background/cron subagents in real-time, without leaving the Control UI.\n\nHow to Open\nClick the updates badge (e.g. \"148 Updates\") in the status bar — opens the update modal and slides in the background sessions panel simultaneously\nCan also be opened programmatically: openBgSessionsPanel(client, state)\nWhat It Shows\nSession list dropdown — all isolated/cron sessions, with 🟢 running / ⚪ idle indicator and a human-readable label extracted from the cron job name\nLive transcript — scrollable, role-colored messages:\n🟣 You (user injections)\n🟢 Agent (assistant responses)\n🟡 → tool (tool calls with args)\n⚪ ← result (tool results, truncated)\n🔴 system (compaction events)\nAuto-refresh — polls bgSessions.history every 3 seconds while panel is open\nSend messages — text area at the bottom; Enter sends, Shift+Enter newlines. Routes through chat.send RPC using the selected sessionKey\nArchitecture\n\nBackend (src/gateway/server-methods/bg-sessions.ts):\n\nbgSessions.list — reads sessions.json for the default agent, filters for isolated/cron session keys (UUID format: agent:main:<uuid>), returns label, updatedAt, running status (lock file presence)\nbgSessions.history — loads the .jsonl transcript file via readSessionMessages(), simplifies to { role, text, timestamp, toolName } array\nbgSessions.send is intentionally omitted — the UI calls chat.send directly with the target sessionKey\n\nController (ui/src/ui/controllers/bg-sessions.ts):\n\nopenBgSessionsPanel(client, state) — sets panel open, fetches sessions, starts 3s poll\ncloseBgSessionsPanel(state) — clears panel open flag, stops poll\nloadBgSessions(client, state) — fetches session list via RPC\nloadBgSessionHistory(client, state, key) — fetches transcript via RPC\nselectBgSession(client, state, key) — changes selected session, reloads history\nsendBgMessage(client, state) — calls chat.send RPC with session key + message, refreshes history\nstartBgSessionsPolling(client, state) / stopBgSessionsPolling() — manage setInterval\n\nView (ui/src/ui/views/bg-sessions.ts):\n\nrenderBgSessionsPanel(state, client) — full Lit HTML panel, rendered as overlay in app-render.ts\nbgSessionsPanelStyles — exported CSS (for reference; styles are embedded in the view's html template)\nPanel is an overlay div (fixed inset-0, z-index 9000) with a right-anchored 520px panel; click outside to close\n\nState fields (added to AppViewState and OpenClawApp):\n\nbgSessionsPanelOpen: boolean;\nbgSessionsList: BgSession[] | null;\nbgSessionsLoading: boolean;\nbgSessionsSelectedKey: string | null;\nbgSessionsHistory: BgMessage[] | null;\nbgSessionsHistoryLoading: boolean;\nbgSessionsInput: string;\nbgSessionsSending: boolean;\n\nWiring in app-render.ts\n\nThe panel is rendered as the last overlay before the closing </div>:\n\n${state.bgSessionsPanelOpen && state.client ? renderBgSessionsPanel(state, state.client) : nothing}\n\n\nThe update badge click handler also calls openBgSessionsPanel:\n\n@click=${() => {\n  // ...existing update modal logic...\n  if ((state as any).client) { openBgSessionsPanel((state as any).client, state as any); }\n}}\n\nMERGE_MANIFEST.json — Add These Entries\n\nWhen merging future upstream changes, protect these new files:\n\n\"src/gateway/server-methods/bg-sessions.ts\": {\n  \"intent\": \"New RPC handlers for background session listing and history (bgSessions.list, bgSessions.history)\",\n  \"strategy\": \"keep-ours\"\n},\n\"ui/src/ui/controllers/bg-sessions.ts\": {\n  \"intent\": \"Controller for the background sessions panel — load, poll, send, select\",\n  \"strategy\": \"keep-ours\"\n},\n\"ui/src/ui/views/bg-sessions.ts\": {\n  \"intent\": \"Right-side drawer panel for viewing/talking to cron subagents\",\n  \"strategy\": \"keep-ours\"\n}\n\n\nAlso add these mustPreserve patterns to the relevant existing protected files:\n\nui/src/ui/app-render.ts: renderBgSessionsPanel, openBgSessionsPanel, bgSessionsPanelOpen\nui/src/ui/app.ts: bgSessionsPanelOpen, bgSessionsList, bgSessionsSelectedKey\nui/src/ui/app-view-state.ts: bgSessionsPanelOpen, bgSessionsList\nUI Update Modal\n\nClicking the topbar update button (in any state) opens an update modal with a guided flow:\n\nCheck for Updates — asks the user to confirm, then calls update.checkUpstream RPC with force: true (bypasses cache)\nStatus Result — shows upstream divergence: commits behind, commits ahead, or \"Up to date\"\nAction Buttons — \"⚡ Run Safe Merge\" (if behind) or \"🔄 Run Merge Anyway\" (if up to date) — both send the merge prompt to the chat session\n\nThe modal is rendered by ui/src/ui/views/update-modal.ts and uses state properties updateModalState (closed/confirm/checking/result), upstreamDivergence, and mergeModel on the app component. A reference copy of the modal source is kept at skills/safe-merge-update/update-modal.ts.\n\nButton States\nN Updates (accent-colored): Git upstream has N newer commits\nUpdates Available (accent-colored): npm registry has a newer version (non-fork workflows)\n✓ Up to Date (muted pill, clickable): Up to date — click still opens the modal to re-check\nMerging… (spinner, disabled): Merge in progress\nHow Update Detection Works\n\nThe gateway runs two parallel checks:\n\nnpm registry (update-startup.ts): Compares VERSION against npm latest. Used for standard installs.\nGit upstream (update.checkUpstream RPC): Runs git fetch upstream && git rev-list --count HEAD..upstream/main. Used for fork workflows. Result is cached for 5 minutes.\n\nFor forks, the git check is authoritative — your local package.json version will often be ahead of npm (since you're building from source), so the npm check would incorrectly say \"up to date.\"\n\nPost-Merge Checklist\n\nAfter a successful merge, always:\n\nRun pnpm ui:build — the Control UI is served from dist/control-ui/\nUpdate systemd service version — the UI header reads OPENCLAW_SERVICE_VERSION from the service unit:\nNEW_VERSION=$(node -e \"console.log(require('./package.json').version)\")\nsed -i \"s/OPENCLAW_SERVICE_VERSION=.*/OPENCLAW_SERVICE_VERSION=$NEW_VERSION/\" ~/.config/systemd/user/openclaw-gateway.service\nsystemctl --user daemon-reload\n\nRun openclaw gateway restart — pick up the new build\nCheck config schema compatibility — upstream may add .strict() to schemas\nClean up backups: rm -rf /tmp/safe-merge/ — while backups are redacted, remove them when no longer needed\nChangelog\n2026-03-02 — Fully Automated Pipeline\nAdded scripts/safe-merge-update.sh — end-to-end automated merge pipeline\nSource changed from origin/main → upstream/main (now pulls real OpenClaw updates)\nFork branch updated to local-desktop-main (was main)\nConflicts auto-resolved by Claude using baked-in strategy (no human needed for happy path)\nAuto-prunes stale safe-merge-* branches on each run (prevents infinite accumulation)\nOn success: pushes to myfork/local-desktop-main, deletes temp branch, restarts gateway\nOn build failure: leaves safe-merge branch intact, exits cleanly for investigation\n--resume flag: skip merge, jump straight to build → push → cleanup (for post-manual-fix)\nMERGE_MANIFEST.json v1.2.0: added hostinger.ts, plugins-ui.ts, memory.ts as protected files\nMemory tab now shows spinner on first load (no more flash of empty content)\n2026-03-01 — Background Sessions Panel\nAdded bgSessions.list and bgSessions.history RPC handlers\nAdded Background Sessions Panel UI (right-side drawer, session selector, live transcript, send input)\nClicking the updates badge now opens the panel alongside the update modal\nState fields: bgSessionsPanelOpen, bgSessionsList, bgSessionsSelectedKey, bgSessionsHistory, bgSessionsInput, bgSessionsSending, bgSessionsHistoryLoading, bgSessionsLoading\nMERGE_MANIFEST.json updated with 3 new protected files and mustPreserve patterns\n2026-03-01 — Merge of 148 upstream commits\n\nConflicts resolved in: app-render.helpers.ts, app-render.ts, app-view-state.ts, app.ts. Key resolutions:\n\napp-render.helpers.ts: kept renderContextGauge, merged hideCron + sessionsHideCron from upstream, merged countHiddenCronSessions, preserved renderRecentArchivedOptions\napp-render.ts: kept our nav imports (getDynamicTabGroups, Jarvis/mode/usage); added upstream's resolveConfiguredCronModelSuggestions\napp-view-state.ts: kept sessionsAgentFilter + added sessionsHideCron from upstream\napp.ts: kept session history fields + added sessionsHideCron = true default from upstream\nKnown Issues / Lessons Learned\nDiscord Voice Schema (2026-02-27)\n\nUpstream added .strict() to DiscordVoiceSchema, rejecting keys our fork previously supported. Fix: remove unsupported keys from config, or add them back to the schema.\n\nControl UI Assets Missing (2026-02-27)\n\npnpm build builds the gateway but NOT the Control UI. UI needs separate pnpm ui:build. The validate script now includes this.\n\nDuplicate Schema Properties (2026-02-27)\n\nGit auto-merge kept both our extracted const AND upstream's inline block. Fix: manual dedup during AI conflict resolution.\n\nCSP connect-src Extensions (2026-03-01)\n\nOur fork adds http://localhost:* (Jarvis voice agent) and https://api.openai.com (Realtime API) to the CSP connect-src directive in control-ui-csp.ts. Upstream only has ws: wss:. On merge, always keep our extensions — they're required for voice features.\n\nBranch Naming Convention (2026-03-01)\n\nMerge branches should be date-stamped: safe-merge-YYYY-MM-DD. This makes it easy to identify merge attempts and clean up old branches.\n\nAuto-Merged Protected Files Need Verification (2026-03-01)\n\nEven files that auto-merge without conflicts can lose custom code if upstream refactors the surrounding context. After merge, always verify mustPreserve patterns exist in auto-merged protected files — don't just trust git's auto-merge.\n\npnpm install May Add Packages (2026-03-01)\n\nUpstream may add new dependencies. When 162 commits are merged, pnpm install downloaded 51 new packages. This is expected but worth noting in the merge report.\n\nSafety Summary\n✅ Backups before any edits — /tmp/safe-merge/backups/\n✅ New branch — never merges directly into current branch\n✅ Validation must pass before committing\n✅ No external downloads in skill setup — skill files are all local; merge workflow does use network for git fetch and pnpm install\n✅ No credential requirements — uses your existing agent model\n✅ Secrets redacted before model transmission — API keys, tokens, passwords, private keys\n✅ Only conflict diffs sent to model — not the entire repo\n✅ User controls merge — agent reports results, user decides to push\n✅ Dry-run in worktree — pre-flight uses a temp worktree, not your working tree\n✅ Stops on failure — if validation fails, reports and stops rather than pushing broken code"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/maverick-software/safe-update-merge",
    "publisherUrl": "https://clawhub.ai/maverick-software/safe-update-merge",
    "owner": "maverick-software",
    "version": "1.3.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/safe-update-merge",
    "downloadUrl": "https://openagent3.xyz/downloads/safe-update-merge",
    "agentUrl": "https://openagent3.xyz/skills/safe-update-merge/agent",
    "manifestUrl": "https://openagent3.xyz/skills/safe-update-merge/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/safe-update-merge/agent.md"
  }
}