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
34 changes: 2 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,50 +46,20 @@ jobs:
chmod +x ci.sh
./ci.sh frontend

electron:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: electron/package-lock.json

- name: Install system dependencies for Electron
run: |
sudo apt-get update
sudo apt-get install -y xvfb libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2

- name: Start Xvfb
run: |
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
echo "DISPLAY=:99" >> $GITHUB_ENV

- name: Run Electron CI
run: |
chmod +x ci.sh
./ci.sh electron

# This job will only run if all jobs succeed
# The workflow will fail if any job fails
check-results:
runs-on: ubuntu-latest
needs: [backend, frontend, electron]
needs: [backend, frontend]
if: always()

steps:
- name: Check all jobs succeeded
run: |
if [[ "${{ needs.backend.result }}" != "success" || "${{ needs.frontend.result }}" != "success" || "${{ needs.electron.result }}" != "success" ]]; then
if [[ "${{ needs.backend.result }}" != "success" || "${{ needs.frontend.result }}" != "success" ]]; then
echo "One or more jobs failed:"
echo "Backend: ${{ needs.backend.result }}"
echo "Frontend: ${{ needs.frontend.result }}"
echo "Electron: ${{ needs.electron.result }}"
exit 1
fi
echo "All jobs succeeded!"
5 changes: 4 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ CHECKPOINT_MIN_CHARACTERS=500
# Provider timeout settings (in milliseconds)
# PROVIDER_TIMEOUT_MS=10000
# PROVIDER_MODEL_FETCH_TIMEOUT_MS=3000
# PROVIDER_STREAM_TIMEOUT_MS=300000

# Playwright settings (for browser-based tools)
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
# PLAYWRIGHT_EXECUTABLE_PATH=/usr/bin/chromium-browser
28 changes: 25 additions & 3 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
ARG NODE_IMAGE=node:20.18.0-alpine3.20
ARG NODE_IMAGE=node:20.18.0-bookworm-slim
FROM ${NODE_IMAGE} AS deps
WORKDIR /app
COPY package*.json ./
RUN chown -R node:node /app
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN mkdir -p $PLAYWRIGHT_BROWSERS_PATH && chown node:node $PLAYWRIGHT_BROWSERS_PATH
USER node
RUN npm ci
RUN npx playwright install chromium

ARG NODE_IMAGE
FROM ${NODE_IMAGE} AS prod-deps
WORKDIR /app
COPY package*.json ./
RUN chown -R node:node /app
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN mkdir -p $PLAYWRIGHT_BROWSERS_PATH && chown node:node $PLAYWRIGHT_BROWSERS_PATH
USER node
RUN npm ci --omit=dev
RUN npx playwright install chromium

ARG NODE_IMAGE
FROM ${NODE_IMAGE} AS dev
WORKDIR /app
ENV NODE_ENV=development
COPY --chown=node:node package*.json ./
RUN apk add --no-cache su-exec sqlite-libs
RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
libsqlite3-0 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
COPY --from=deps --chown=node:node $PLAYWRIGHT_BROWSERS_PATH $PLAYWRIGHT_BROWSERS_PATH
RUN npx playwright install-deps chromium
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node . .
RUN chmod +x entrypoint.sh
Expand All @@ -38,7 +52,15 @@ ENV PORT=3001
ENV INSTALL_ON_START=0
COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node . .
RUN apk add --no-cache su-exec
RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
libsqlite3-0 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
COPY --from=prod-deps --chown=node:node $PLAYWRIGHT_BROWSERS_PATH $PLAYWRIGHT_BROWSERS_PATH
RUN npx playwright install-deps chromium
RUN chmod +x entrypoint.sh
RUN mkdir -p logs && chown -R node:node logs
USER node
Expand Down
43 changes: 26 additions & 17 deletions backend/__tests__/web_fetch_enhanced.test.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,56 @@
import { webFetchTool } from '../src/lib/tools/webFetch.js';

describe('web_fetch enhanced features', () => {
describe('heading_range parameter', () => {
it('should validate heading_range structure', () => {
describe('heading parameter as array', () => {
it('should validate heading as an array of strings', () => {
expect(
webFetchTool.validate({
url: 'https://example.com',
heading_range: { start: 1, end: 3 }
heading: ['Introduction', 'Usage']
})
).toEqual({
url: 'https://example.com',
maxChars: 10000,
targetHeading: null,
headingRange: { start: 1, end: 3 }
targetHeadings: ['Introduction', 'Usage'],
useBrowser: false
});
});

it('should reject invalid heading_range', () => {
expect(() =>
it('should validate heading as an array of numbers', () => {
expect(
webFetchTool.validate({
url: 'https://example.com',
heading_range: { start: 0, end: 3 }
heading: [1, 3]
})
).toThrow('heading_range must have start >= 1');
).toEqual({
url: 'https://example.com',
maxChars: 10000,
targetHeadings: [1, 3],
useBrowser: false
});
});

it('should reject heading_range with end < start', () => {
expect(() =>
it('should validate heading as a single string (backward compatibility)', () => {
expect(
webFetchTool.validate({
url: 'https://example.com',
heading_range: { start: 5, end: 3 }
heading: 'Introduction'
})
).toThrow('heading_range must have start >= 1 and end >= start');
).toEqual({
url: 'https://example.com',
maxChars: 10000,
targetHeadings: ['Introduction'],
useBrowser: false
});
});

it('should reject both heading and heading_range', () => {
it('should reject invalid heading types', () => {
expect(() =>
webFetchTool.validate({
url: 'https://example.com',
heading: 'Introduction',
heading_range: { start: 1, end: 3 }
heading: { name: 'Intro' }
})
).toThrow('Cannot use both "heading" and "heading_range" parameters');
).toThrow('heading must be a string, number, or an array of strings/numbers');
});
});

Expand Down
2 changes: 1 addition & 1 deletion backend/__tests__/web_search_searxng_tool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('web_search_searxng tool', () => {
test('validate rejects empty string for categories', () => {
assert.throws(
() => webSearchSearxngTool.validate({ query: 'test', categories: ' ' }),
/categories must be a non-empty string/
/category must be a non-empty string/
);
});

Expand Down
Loading
Loading