Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 0 additions & 58 deletions .github/workflows/cla.yml

This file was deleted.

42 changes: 0 additions & 42 deletions .github/workflows/claude.yml

This file was deleted.

106 changes: 9 additions & 97 deletions .github/workflows/upstream-sync.yml
Original file line number Diff line number Diff line change
@@ -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."
38 changes: 38 additions & 0 deletions docs/fork-lifecycle.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -26,6 +26,25 @@ 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,
useConnectBrowseros: () => ({
isPending: false,
variables: undefined,
mutateAsync: async () => ({ installed: true }),
}),
useDisconnectBrowseros: () => ({
isPending: false,
variables: undefined,
mutateAsync: async () => ({ installed: false }),
}),
}))

const { Cockpit } = await import('./Cockpit')

function renderApp(): string {
Expand All @@ -42,12 +61,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', () => {
Expand All @@ -56,10 +75,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')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Loading