From 3bf36750dd760c27862a860f7086e68815ebc4dd Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sun, 12 Jul 2026 10:36:51 +0700 Subject: [PATCH 1/3] test(cockpit): isolate onboarding rendering --- .../claw-app/screens/cockpit/Cockpit.test.tsx | 23 +++++++++++++------ .../apps/claw-app/screens/cockpit/Cockpit.tsx | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx b/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx index 143a36a70..efa3b10d1 100644 --- a/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx +++ b/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx @@ -1,4 +1,4 @@ -/** Pins the Claw homepage's hero, running grid, and recent activity sections. */ +/** Pins the first-run Cockpit onboarding state for an empty repository. */ import { describe, expect, it, mock } from 'bun:test' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' @@ -26,6 +26,15 @@ mock.module('@/modules/api/audit.hooks', () => ({ useTaskScreenshotBaseUrl: () => null, })) +const useBrowserosConnections = Object.assign( + () => ({ data: { connections: [] }, isPending: false }), + { getKey: () => ['browseros-connections'] }, +) + +mock.module('@/modules/api/connections.hooks', () => ({ + useBrowserosConnections, +})) + const { Cockpit } = await import('./Cockpit') function renderApp(): string { @@ -42,12 +51,12 @@ function renderApp(): string { } describe('Cockpit (v2)', () => { - it('renders the hero and activity header (running grid hides when no agents)', () => { + it('renders first-run onboarding when no agents or activity exist', () => { const html = renderApp() - expect(html).toContain('working on') - expect(html).toContain('Recent activity') - // No agents in the stub data means RunningGrid returns null. + expect(html).toContain('Get started') + expect(html).toContain('Ask your agent to try it.') expect(html).not.toContain('Running now') + expect(html).not.toContain('Recent activity') }) it('does NOT render an add-profile tile in the default v2 build', () => { @@ -56,10 +65,10 @@ describe('Cockpit (v2)', () => { expect(html).not.toContain('harness . logins . guardrails') }) - it('shows only the recent-activity empty state when registry is empty (Running now hides)', () => { + it('hides ready-state empty sections during first-run onboarding', () => { const html = renderApp() expect(html).not.toContain('No agents connected') expect(html).not.toContain('Running now') - expect(html).toContain('No recent activity') + expect(html).not.toContain('No recent activity') }) }) diff --git a/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.tsx b/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.tsx index 0f8a44641..1a73ca6a3 100644 --- a/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.tsx +++ b/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.tsx @@ -25,7 +25,7 @@ export function Cockpit() { variables: { limit: ONBOARDING_PROBE_LIMIT }, }) // Only count harnesses that appear on the /mcp screen. Hidden ones - // (Hermes, OpenClaw, Gemini CLI, retired Claude Desktop) may be + // (Hermes, OpenClaw, retired Google CLI aliases, retired Claude Desktop) may be // preinstalled but are never something the reader intentionally // connected, so lighting up 'MCP installed' for them is misleading. const hasConnection = From bd5370bf943011ae68d1bbdebdd95fee228c9f2d Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sun, 12 Jul 2026 10:36:52 +0700 Subject: [PATCH 2/3] fix(ci): quarantine unsafe fork automation --- .github/workflows/cla.yml | 58 --------------- .github/workflows/claude.yml | 42 ----------- .github/workflows/upstream-sync.yml | 106 +++------------------------- docs/fork-lifecycle.md | 38 ++++++++++ 4 files changed, 47 insertions(+), 197 deletions(-) delete mode 100644 .github/workflows/cla.yml delete mode 100644 .github/workflows/claude.yml create mode 100644 docs/fork-lifecycle.md diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml deleted file mode 100644 index 3c90ebe50..000000000 --- a/.github/workflows/cla.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: CLA Assistant - -on: - issue_comment: - types: [created] - pull_request_target: - types: [opened, closed, synchronize] - -permissions: - actions: write - contents: write - pull-requests: write - statuses: write - -jobs: - cla: - runs-on: ubuntu-latest - if: | - (github.event_name == 'pull_request_target') || - (github.event_name == 'issue_comment' && github.event.issue.pull_request && - (github.event.comment.body == 'recheck' || - github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA')) - steps: - - name: CLA Assistant - uses: contributor-assistant/github-action@v2.6.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGNATURES_TOKEN }} - with: - path-to-signatures: 'cla-signatures.json' - path-to-document: 'https://github.com/${{ github.repository }}/blob/main/CLA.md' - branch: 'main' - remote-organization-name: 'browseros-ai' - remote-repository-name: 'cla-signatures' - allowlist: 'shadowfax92,felarof99,bot*,*[bot],dependabot,renovate,github-actions,snyk-bot,imgbot,greenkeeper,semantic-release-bot,allcontributors' - lock-pullrequest-aftermerge: false - custom-notsigned-prcomment: | - Thank you for your contribution! Before we can merge this PR, we need you to sign our [Contributor License Agreement](https://github.com/${{ github.repository }}/blob/main/CLA.md). - - **To sign the CLA**, please add a comment to this PR with the following text: - - ``` - I have read the CLA Document and I hereby sign the CLA - ``` - - You only need to sign once. After signing, this check will pass automatically. - - --- -
- Troubleshooting - - - **Already signed but still failing?** Comment `recheck` to trigger a re-verification. - - **Signed with a different email?** Make sure your commit email matches your GitHub account email, or add your commit email to your GitHub account. - -
- custom-pr-sign-comment: 'I have read the CLA Document and I hereby sign the CLA' - custom-allsigned-prcomment: | - All contributors have signed the CLA. Thank you! diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index 47cad0ced..000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Claude Code - -on: - pull_request: - types: [opened, ready_for_review] - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - github.event_name == 'pull_request' || - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - issues: read - id-token: write - actions: read - steps: - - name: Checkout repository - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1.0.159 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - additional_permissions: | - actions: read diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml index 16720b35d..6807728ea 100644 --- a/.github/workflows/upstream-sync.yml +++ b/.github/workflows/upstream-sync.yml @@ -1,108 +1,20 @@ -name: Upstream Sync +name: Upstream Sync (Quarantined) -# Keep NDDev-it-com/nddev-browser automatically synchronized with the upstream -# browseros-ai/BrowserOS while preserving this fork's own commits (the -# self-contained submodule removal and the CloakBrowser CDP routing). -# -# Strategy: merge (never fast-forward, because the fork has diverged; never -# force-push). A clean merge is pushed straight to main so the fork stays -# current hands-off. A conflicting merge is aborted and reported as a tracking -# issue for manual resolution, so upstream changes never clobber the fork. +# The owned fork and browseros-ai/BrowserOS currently have no common ancestor. +# Automatic merge, force update, and unrelated-history merge are forbidden until +# the maintained-patch migration is planned and reviewed explicitly. on: - schedule: - # Every 6 hours, offset from the top of the hour. - - cron: '17 */6 * * *' workflow_dispatch: - inputs: - upstream_ref: - description: 'Upstream ref to sync from' - required: false - default: 'main' - -concurrency: - group: upstream-sync - cancel-in-progress: false permissions: - contents: write - issues: write - -env: - UPSTREAM_REPO: browseros-ai/BrowserOS - UPSTREAM_REF: ${{ github.event.inputs.upstream_ref || 'main' }} + contents: read jobs: - sync: - name: Merge upstream into main + quarantine: + name: Report quarantined topology runs-on: ubuntu-latest steps: - - name: Checkout fork main - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure git identity - run: | - git config user.name 'nddev-browser-sync[bot]' - git config user.email 'nddev-browser-sync@users.noreply.github.com' - - - name: Fetch upstream - run: | - git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" - git fetch upstream "${UPSTREAM_REF}" --no-tags - - - name: Attempt merge - id: merge - run: | - set -o pipefail - upstream_sha="$(git rev-parse "upstream/${UPSTREAM_REF}")" - echo "upstream_sha=${upstream_sha}" >> "$GITHUB_OUTPUT" - - if git merge-base --is-ancestor "${upstream_sha}" HEAD; then - echo "status=up-to-date" >> "$GITHUB_OUTPUT" - echo "Fork already contains upstream ${upstream_sha}." - exit 0 - fi - - if git merge --no-edit --no-ff \ - -m "chore(sync): merge upstream ${UPSTREAM_REPO}@${upstream_sha:0:9}" \ - "${upstream_sha}"; then - echo "status=merged" >> "$GITHUB_OUTPUT" - else - git merge --abort || true - echo "status=conflict" >> "$GITHUB_OUTPUT" - fi - - - name: Push merged main - if: steps.merge.outputs.status == 'merged' - run: git push origin HEAD:main - - - name: Report conflict as tracking issue - if: steps.merge.outputs.status == 'conflict' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - UPSTREAM_SHA: ${{ steps.merge.outputs.upstream_sha }} + - name: Explain the stop condition run: | - short="${UPSTREAM_SHA:0:9}" - title="Upstream sync conflict: ${UPSTREAM_REPO}@${short}" - { - echo "Automatic merge of \`${UPSTREAM_REPO}@${UPSTREAM_SHA}\` into \`main\` hit conflicts and was aborted (no force-push; the fork commits are preserved)." - echo - echo "Resolve locally, keeping the CloakBrowser CDP routing and the self-contained fork changes:" - echo - echo '```bash' - echo "git remote add upstream https://github.com/${UPSTREAM_REPO}.git" - echo "git fetch upstream ${UPSTREAM_REF}" - echo "git merge upstream/${UPSTREAM_REF}" - echo "git push origin main" - echo '```' - } > issue-body.md - existing="$(gh issue list --state open --search 'in:title Upstream sync conflict' --json number --jq '.[0].number' || true)" - if [ -n "${existing}" ]; then - gh issue comment "${existing}" --body-file issue-body.md - else - gh issue create --title "${title}" --body-file issue-body.md - fi - rm -f issue-body.md + echo "::notice title=Upstream sync quarantined::The fork and upstream have no common ancestor. Follow docs/fork-lifecycle.md; do not force or merge unrelated histories." diff --git a/docs/fork-lifecycle.md b/docs/fork-lifecycle.md new file mode 100644 index 000000000..b3351d0d4 --- /dev/null +++ b/docs/fork-lifecycle.md @@ -0,0 +1,38 @@ +# Fork lifecycle + +## Current state + +`NDDev-it-com/nddev-browser` is an owned maintained-patch fork of +`browseros-ai/BrowserOS`. The current default branches have no common ancestor, +so an ordinary merge cannot synchronize them safely. + +Automatic upstream synchronization is quarantined. The repository must not: + +- force-update the owned default branch; +- merge unrelated histories; +- discard fork-specific commits; +- claim that the upstream relationship is synchronized. + +## Recovery decision + +Before upstream integration resumes, create an explicit GDS fork plan that: + +1. inventories every owned commit and required patch; +2. selects exact patches to transplant onto a fresh upstream base; +3. validates the rebuilt repository and fork-specific behavior; +4. preserves a recoverable reference to the current history; +5. obtains approval for any default-branch replacement; +6. verifies provider and local topology after apply. + +Until that plan is approved, `fork.policy: maintained-patch` remains canonical +and the sync workflow is informational only. + +## Fork-only automation + +The inherited upstream CLA workflow is not valid in this fork because its +signature store and token belong to upstream infrastructure. CLA enforcement +will occur in the upstream repository when a contribution is submitted there. + +The inherited Claude workflow is also disabled in this fork until an owned App +installation, credential, permission set, and immutable action pin are reviewed +and configured explicitly. From dbd27584662d48a6321ba80b0411172b994ecfb0 Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sun, 12 Jul 2026 10:44:58 +0700 Subject: [PATCH 3/3] test(claw-app): preserve connection hook mock contract --- .../apps/claw-app/screens/cockpit/Cockpit.test.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx b/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx index efa3b10d1..a81c68196 100644 --- a/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx +++ b/packages/browseros-agent/apps/claw-app/screens/cockpit/Cockpit.test.tsx @@ -33,6 +33,16 @@ const useBrowserosConnections = Object.assign( mock.module('@/modules/api/connections.hooks', () => ({ useBrowserosConnections, + useConnectBrowseros: () => ({ + isPending: false, + variables: undefined, + mutateAsync: async () => ({ installed: true }), + }), + useDisconnectBrowseros: () => ({ + isPending: false, + variables: undefined, + mutateAsync: async () => ({ installed: false }), + }), })) const { Cockpit } = await import('./Cockpit')