mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
* test(desktop): e2e test for interim assistant message preservation (#65919) Adds a Playwright E2E test that reproduces the fix from PR #65919 across all three layers (agent core → tui_gateway → desktop renderer). The mock inference server is upgraded with a multi-turn scripted response that exercises several interleaved patterns: 1. text + tool_call → should produce an interim message 2. text + tool_call → another interim message 3. no text + tool_call → NO interim (no visible text alongside tools) 4. text + tool_call → another interim message 5. final answer (stop) → message.complete, different from all interims Two describe blocks exercise display.interim_assistant_messages both on (default) and off: - ON: all interim texts + the final answer visible in the transcript - OFF: only the final answer visible, all interim texts wiped Also fixes a footgun: test:e2e now runs `npm run build` as a pretest hook so the renderer dist/ is always fresh. Previously, running `npx playwright test` locally would silently load a stale dist/ that predated renderer fixes — the python backend ran from source (had the fix) but the renderer was frozen in an old bundle. CI already built fresh, so the explicit build step there is removed to avoid duplication. * test(desktop): e2e sidebar states — background dot, subagent, cross-session Add sidebar-states.spec.ts with three E2E tests exercising the desktop sidebar's session dot states driven by real gateway events: 1. Background process dot appears during a terminal(background=true) call and disappears after auto-dismiss; subagent (delegate_task) runs concurrently; final answer is visible in the transcript. 2. Background dot remains visible while a subagent runs concurrently (longer sleep 5 background process so the dot is catchable). 3. Cross-session dot transition: start a turn with a background process, wait for the turn to complete, open a new session, then verify the original session's dot transitions from 'background running' to 'finished — unread' when the background process exits. The mock server gains SIDEBAR_SCRIPT and SIDEBAR_CROSS_SCRIPT trigger keywords that return tool_calls for terminal(background=true) and delegate_task — the agent executes these for real (real background process, real subagent), so the tests assert against genuine gateway events rather than mocked UI state. Verified: 3 passed (1.2m) under cage headless wlroots. * test(desktop): e2e tests for tile-unread bug (tab passes, split fails) Two scenarios for the tile-unread bug where a session that finishes while visible on-screen gets the green 'finished unread' dot even though the user is looking right at it. The unread check in handleTransition (session-states.ts:174) only compares against $selectedStoredSessionId and ignores $sessionTiles, so a session visible in a tile gets marked unread even though it's on screen. 1. TAB (hidden, PASSES): ⌃-click opens the session as a stacked tab that is NOT visible on screen. The unread dot IS correct here — the user isn't looking at it. 2. SPLIT (visible, FAILS): drag the session row to the workspace's right edge to create a side-by-side split tile. Both sessions are visible on screen. The unread dot is WRONG — the session is visible in the split tile, so it should not be marked 'unread'. This test is RED until the fix lands. Also adds explicit page.screenshot() calls at key assertion points in sidebar-states.spec.ts so the trace viewer has full-res captures of the sidebar dot states during the test. * test(desktop): cover compression and queued stop lifecycle Add real desktop E2E coverage for session compression continuation and queue parking after an explicit Stop. Extend the mock server with a blocking scripted turn and submitted-prompt assertions. * test(desktop): cover busy composer submit routing Replace the invalid queued-stop E2E scenario: plain text redirects a busy turn rather than entering the queue. Add focused submit-routing coverage for plain text, slash commands, attachments, explicit Stop, and idle submission.
277 lines
10 KiB
JSON
277 lines
10 KiB
JSON
{
|
|
"name": "hermes",
|
|
"productName": "Hermes",
|
|
"private": true,
|
|
"version": "0.17.0",
|
|
"description": "Native desktop shell for Hermes Agent.",
|
|
"author": "Nous Research",
|
|
"type": "module",
|
|
"main": "dist/electron-main.mjs",
|
|
"engines": {
|
|
"node": "^20.19.0 || >=22.12.0"
|
|
},
|
|
"scripts": {
|
|
"clean": "npm run clean:e2e && npm run clean:renderer && npm run clean:electron",
|
|
"clean:e2e":"tsc --build tsconfig.e2e.json --clean",
|
|
"clean:renderer":"tsc --build tsconfig.json --clean ",
|
|
"clean:electron":"tsc --build tsconfig.electron.json --clean",
|
|
"dev": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"",
|
|
"dev:fake-boot": "cross-env HERMES_DESKTOP_BOOT_FAKE=1 HERMES_DESKTOP_BOOT_FAKE_STEP_MS=650 npm run dev",
|
|
"dev:mock": "node scripts/dev-mock.mjs",
|
|
"dev:renderer": "node scripts/assert-root-install.mjs && npm run clean:renderer && vite --host 127.0.0.1 --port 5174",
|
|
"dev:electron": "tsc --build tsconfig.electron.json && wait-on http://127.0.0.1:5174 && node scripts/bundle-electron-main.mjs --dev && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
|
|
"profile:main": "tsc --build tsconfig.electron.json && wait-on http://127.0.0.1:5174 && node scripts/bundle-electron-main.mjs --dev && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron --inspect=9229 .",
|
|
"profile:main:cpu": "tsc --build tsconfig.electron.json && wait-on http://127.0.0.1:5174 && node scripts/bundle-electron-main.mjs --dev && cross-env XCURSOR_SIZE=24 NODE_OPTIONS=--cpu-prof HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
|
|
"start": "npm run build && electron .",
|
|
"prebuild": "npm run clean",
|
|
"build": "node scripts/assert-root-install.mjs && node scripts/write-build-stamp.mjs && vite build && node scripts/bundle-electron-main.mjs && node scripts/stage-native-deps.mjs",
|
|
"postbuild": "node scripts/assert-dist-built.mjs",
|
|
"prebuilder": "node scripts/patch-electron-builder-mac-binary.mjs",
|
|
"builder": "cross-env NODE_OPTIONS=--max-old-space-size=16384 node scripts/run-electron-builder.mjs",
|
|
"pack": "npm run build && npm run builder -- --dir",
|
|
"dist": "npm run build && npm run builder",
|
|
"dist:mac": "npm run build && npm run builder -- --mac",
|
|
"dist:mac:dmg": "npm run build && npm run builder -- --mac dmg",
|
|
"dist:mac:zip": "npm run build && npm run builder -- --mac zip",
|
|
"dist:win": "npm run build && npm run builder -- --win",
|
|
"dist:win:msi": "npm run build && npm run builder -- --win msi",
|
|
"dist:win:nsis": "npm run build && npm run builder -- --win nsis",
|
|
"dist:linux": "npm run build && npm run builder -- --linux AppImage deb rpm",
|
|
"perf": "node scripts/perf/run.mjs",
|
|
"perf:serve": "node scripts/perf/serve.mjs",
|
|
"test:desktop": "node scripts/test-desktop.mjs",
|
|
"test:desktop:all": "node scripts/test-desktop.mjs all",
|
|
"test:desktop:dmg": "node scripts/test-desktop.mjs dmg",
|
|
"test:desktop:nsis": "node scripts/test-desktop.mjs nsis",
|
|
"test:desktop:existing": "node scripts/test-desktop.mjs existing",
|
|
"test:desktop:fresh": "node scripts/test-desktop.mjs fresh",
|
|
"typecheck": "tsc -p . --noEmit && tsc -p tsconfig.electron.json --noEmit && tsc -p tsconfig.e2e.json --noEmit",
|
|
"lint": "eslint src/ electron/",
|
|
"lint:fix": "eslint src/ electron/ --fix",
|
|
"fmt": "prettier --write 'src/**/*.{ts,tsx}' 'electron/**/*.ts' 'vite.config.ts'",
|
|
"fix": "npm run lint:fix && npm run fmt",
|
|
"test:ui": "vitest run --project ui",
|
|
"test:desktop:platforms": "vitest run --project electron",
|
|
"test": "vitest run",
|
|
"preview": "node scripts/assert-root-install.mjs && vite preview --host 127.0.0.1 --port 4174",
|
|
"check": "npm run typecheck && npm run test && npm run test:desktop:all",
|
|
"test:e2e": "npm run build && playwright test e2e/",
|
|
"test:e2e:visual": "npm run build && WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list",
|
|
"test:e2e:update-snapshots": "npm run build && WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list --update-snapshots"
|
|
},
|
|
"dependencies": {
|
|
"@assistant-ui/react": "^0.14.23",
|
|
"@assistant-ui/react-streamdown": "^0.3.4",
|
|
"@audiowave/react": "^0.6.2",
|
|
"@chenglou/pretext": "^0.0.6",
|
|
"@codemirror/commands": "^6.10.4",
|
|
"@codemirror/language": "^6.12.4",
|
|
"@codemirror/language-data": "^6.5.2",
|
|
"@codemirror/state": "^6.7.0",
|
|
"@codemirror/view": "^6.43.3",
|
|
"@dnd-kit/core": "^6.3.1",
|
|
"@dnd-kit/sortable": "^10.0.0",
|
|
"@dnd-kit/utilities": "^3.2.2",
|
|
"@hermes/shared": "file:../shared",
|
|
"@icons-pack/react-simple-icons": "=13.11.1",
|
|
"@lezer/highlight": "^1.2.3",
|
|
"@nanostores/react": "^1.1.0",
|
|
"@nous-research/ui": "^0.13.0",
|
|
"@radix-ui/react-slot": "^1.2.4",
|
|
"@streamdown/code": "^1.1.1",
|
|
"@tabler/icons-react": "^3.41.1",
|
|
"@tailwindcss/typography": "^0.5.19",
|
|
"@tailwindcss/vite": "^4.2.4",
|
|
"@tanstack/react-query": "^5.100.6",
|
|
"@tanstack/react-virtual": "^3.13.24",
|
|
"@vscode/codicons": "^0.0.45",
|
|
"@xterm/addon-fit": "^0.11.0",
|
|
"@xterm/addon-serialize": "^0.14.0",
|
|
"@xterm/addon-unicode11": "^0.9.0",
|
|
"@xterm/addon-web-links": "^0.12.0",
|
|
"@xterm/addon-webgl": "^0.19.0",
|
|
"@xterm/xterm": "^6.0.0",
|
|
"class-variance-authority": "^0.7.1",
|
|
"clsx": "^2.1.1",
|
|
"cmdk": "^1.1.1",
|
|
"d3-force": "^3.0.0",
|
|
"dnd-core": "^14.0.1",
|
|
"dompurify": "^3.4.11",
|
|
"fflate": "^0.8.3",
|
|
"hast-util-from-html-isomorphic": "^2.0.0",
|
|
"hast-util-to-text": "^4.0.2",
|
|
"ignore": "^7.0.5",
|
|
"katex": "^0.16.45",
|
|
"leva": "^0.10.1",
|
|
"mermaid": "^11.15.0",
|
|
"motion": "^12.38.0",
|
|
"nanostores": "^1.3.0",
|
|
"node-pty": "1.1.0",
|
|
"radix-ui": "^1.4.3",
|
|
"react": "^19.2.5",
|
|
"react-arborist": "^3.5.0",
|
|
"react-dnd-html5-backend": "^14.0.3",
|
|
"react-dom": "^19.2.5",
|
|
"react-router-dom": "^7.17.0",
|
|
"react-shiki": "^0.9.3",
|
|
"remark-math": "^6.0.0",
|
|
"remend": "^1.3.0",
|
|
"shiki": "^4.0.2",
|
|
"simple-git": "^3.36.0",
|
|
"streamdown": "^2.5.0",
|
|
"tailwind-merge": "^3.5.0",
|
|
"tailwindcss": "^4.2.4",
|
|
"tw-shimmer": "^0.4.11",
|
|
"unicode-animations": "^1.0.3",
|
|
"unified": "^11.0.5",
|
|
"unist-util-visit-parents": "^6.0.2",
|
|
"use-stick-to-bottom": "^1.1.6",
|
|
"vfile": "^6.0.3",
|
|
"web-haptics": "^0.0.6"
|
|
},
|
|
"devDependencies": {
|
|
"@electron/rebuild": "^4.0.6",
|
|
"@eslint/js": "^9.39.4",
|
|
"@playwright/test": "=1.58.2",
|
|
"@testing-library/dom": "^10.4.0",
|
|
"@testing-library/react": "^16.3.2",
|
|
"@types/d3-force": "^3.0.10",
|
|
"@types/hast": "^3.0.4",
|
|
"@types/node": "^22.20.0",
|
|
"@types/react": "^19.2.14",
|
|
"@types/react-dom": "^19.2.3",
|
|
"@typescript-eslint/eslint-plugin": "^8.59.1",
|
|
"@typescript-eslint/parser": "^8.59.1",
|
|
"@vitejs/plugin-react": "^6.0.1",
|
|
"concurrently": "^10.0.3",
|
|
"cross-env": "^10.1.0",
|
|
"electron": "40.10.2",
|
|
"electron-builder": "^26.8.1",
|
|
"esbuild": "^0.28.1",
|
|
"eslint": "^9.39.4",
|
|
"eslint-plugin-perfectionist": "^5.9.0",
|
|
"eslint-plugin-react": "^7.37.5",
|
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
"globals": "^16.5.0",
|
|
"jsdom": "^29.1.1",
|
|
"prettier": "^3.8.3",
|
|
"rcedit": "^5.0.2",
|
|
"tsx": "^4.22.4",
|
|
"typescript": "^6.0.3",
|
|
"vite": "^8.0.10",
|
|
"vitest": "^4.1.5",
|
|
"wait-on": "^9.0.5"
|
|
},
|
|
"build": {
|
|
"electronVersion": "40.10.2",
|
|
"appId": "com.nousresearch.hermes",
|
|
"productName": "Hermes",
|
|
"executableName": "Hermes",
|
|
"protocols": [
|
|
{
|
|
"name": "Hermes Protocol",
|
|
"schemes": [
|
|
"hermes"
|
|
]
|
|
}
|
|
],
|
|
"artifactName": "Hermes-${version}-${os}-${arch}.${ext}",
|
|
"icon": "assets/icon",
|
|
"directories": {
|
|
"output": "release"
|
|
},
|
|
"files": [
|
|
"dist/**",
|
|
"assets/**",
|
|
"public/**",
|
|
"package.json"
|
|
],
|
|
"beforeBuild": "scripts/before-build.mjs",
|
|
"beforePack": "scripts/before-pack.mjs",
|
|
"afterPack": "scripts/after-pack.mjs",
|
|
"extraResources": [
|
|
{
|
|
"from": "build/install-stamp.json",
|
|
"to": "install-stamp.json"
|
|
},
|
|
{
|
|
"from": "assets/icon.ico",
|
|
"to": "icon.ico"
|
|
}
|
|
],
|
|
"asar": true,
|
|
"afterSign": "scripts/notarize.mjs",
|
|
"asarUnpack": [
|
|
"**/*.node",
|
|
"**/prebuilds/**",
|
|
"dist/**"
|
|
],
|
|
"mac": {
|
|
"category": "public.app-category.developer-tools",
|
|
"entitlements": "electron/entitlements.mac.plist",
|
|
"entitlementsInherit": "electron/entitlements.mac.inherit.plist",
|
|
"extendInfo": {
|
|
"CFBundleDisplayName": "Hermes",
|
|
"CFBundleExecutable": "Hermes",
|
|
"CFBundleName": "Hermes",
|
|
"NSAudioCaptureUsageDescription": "Hermes uses audio capture for voice conversations.",
|
|
"NSMicrophoneUsageDescription": "Hermes uses the microphone for voice input and voice conversations."
|
|
},
|
|
"gatekeeperAssess": false,
|
|
"hardenedRuntime": true,
|
|
"target": [
|
|
"dmg",
|
|
"zip"
|
|
]
|
|
},
|
|
"dmg": {
|
|
"title": "Install Hermes",
|
|
"backgroundColor": "#f5f5f7",
|
|
"iconSize": 96,
|
|
"window": {
|
|
"width": 560,
|
|
"height": 360
|
|
},
|
|
"contents": [
|
|
{
|
|
"x": 160,
|
|
"y": 170,
|
|
"type": "file"
|
|
},
|
|
{
|
|
"x": 400,
|
|
"y": 170,
|
|
"type": "link",
|
|
"path": "/Applications"
|
|
}
|
|
]
|
|
},
|
|
"win": {
|
|
"legalTrademarks": "Hermes",
|
|
"target": [
|
|
"nsis",
|
|
"msi"
|
|
],
|
|
"signAndEditExecutable": false
|
|
},
|
|
"linux": {
|
|
"category": "Development",
|
|
"maintainer": "Nous Research <support@nousresearch.com>",
|
|
"synopsis": "Native desktop shell for Hermes Agent.",
|
|
"target": [
|
|
"AppImage",
|
|
"deb",
|
|
"rpm"
|
|
]
|
|
},
|
|
"nsis": {
|
|
"oneClick": false,
|
|
"allowToChangeInstallationDirectory": true,
|
|
"perMachine": false,
|
|
"shortcutName": "Hermes",
|
|
"uninstallDisplayName": "Hermes",
|
|
"warningsAsErrors": false
|
|
}
|
|
}
|
|
}
|