write-build-stamp.mjs rewrites the stamp's `builtAt` clock on every build, so the
whole-file hash moved under a receipt that had just recorded it. Two builds
overlapping on one tree failed with "Build inputs changed during compilation" for
a difference the build machinery itself made, and the caller turned that into
SystemExit(1).
Hash the stamp's provenance identity instead, key-sorted so a cosmetic
re-serialisation is not a change. The clock is excluded from that comparison but
recorded as `stampClock` — the value the bake actually embedded, captured from
the same read that produces __HERMES_INSTALL_STAMP__ — because packaging copies
the live stamp into the bundle after the receipt is written. Re-reading the stamp
at record time would certify a clock the output never contained, and
productCurrent would then report a bundle current whose packaged clock disagrees
with its baked main, which is exactly what detectBundleSwap must never do.
recordProduct still compares the prepared inputs, so a clock-only restamp cannot
throw. productCurrent additionally requires the recorded clock to match, so a
restamp between bake and packaging is a rebuild rather than a silent divergence.
A receipt predating the field compares by hash alone until the next build
rewrites it; the release packaging path never consults productCurrent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main removed pruneComposerPopoutZones from @/store/composer-popout (the
popout zones were reworked); dead-session drafts are pruned through
composer.ts's stashedDraftScopes/clearSessionDraft instead. The import was
unused and broke the desktop typecheck.
Pins, composer drafts, and queued prompts persist session ids in
localStorage and re-assert/probe them at boot. Sessions removed from
state.db (retention purge, manual delete) leave dead ids behind:
nothing ever drops them, so every boot re-requests the dead ids and the
gateway answers 404 "Session not found" once per stale reference,
forever.
Add a dead-session prune sweep that runs after the first sidebar list
payload (and re-runs, debounced, on later list changes). A stored id is
only declared dead after a by-id probe 404s on every known profile — a
404 is profile-scoped, so a single miss never prunes anything, and
non-404 probe failures (gateway mid-restart, network) defer the id
instead of dropping it. Dead pins are forgotten from pin-sync
bookkeeping before unpinning so the reconcile does not re-PATCH the
dead id (which would recreate the 404).
Fixes#79001
preprocessMarkdown and normalizeFilePreviewMath told code from prose with
CODE_FENCE_SPLIT_RE, which ends a fenced block at the first ``` or ~~~
anywhere, mid-line included, and whatever the opening marker was. The
rest of the listing then ran through the prose rewrites: citation
stripping turned arr[0] into arr, and $5 became \$5. EMPTY_FENCE_BLOCK_RE
matched an inner closing fence followed by the outer one, so a markdown
example fenced with ```` or ~~~ lost both closers, and the rest of the
message rendered inside the code block.
Both now use one line-based splitter with the CommonMark fence rule that
normalizeFenceBlocks already applies: a fence opens on its own line, and
only a line holding the same character, at least as long, closes it.
A backend without the batch wire parks one single-shape gateway request
per question while the tool args carry the canonical one-entry questions
array. The batch card has no qids to answer with, so it sat a
permanently-disabled '0 of 1 answered' preview until the live request
timed out unanswered. Route that skew to the live single card, whose own
question-text match still stands down on disagreement.
Closes#123126
ar.ts had grown to 3,770 lines (2K-law violation, #79963). Split the
Arabic catalog into topic siblings and recompose it in ar.ts via
defineLocale():
ar.ts -> ar_artifacts.ts (artifacts, artifactCard, artifactPreview, preview)
ar_assistant.ts (assistant)
ar_boot.ts (boot, remoteDisplayBanner, updates, guidedGreeting, install, onboarding)
ar_capabilities.ts (catalog, skills, agents)
ar_chat.ts (composer, statusStack, prompts, desktop, tips)
ar_chrome.ts (externalOpenFailed, fileMenu, titlebar, keybinds, sidebar, shell, rightSidebar, zones, contextMenu)
ar_command_center.ts (commandCenter, messaging, profiles, cron)
ar_common.ts (sharedMetrics, common, ui)
ar_connectors.ts (sessionImport)
ar_diagnostics.ts (sendDiagnostics, notifications, errors)
ar_settings.ts (language, settings, modelAssignment, modelPicker, modelVisibility, interfaceMode)
Pure move: every member block moved byte-verbatim; the composed catalog
is value-identical to HEAD's ar.ts (4,935 leaf keys). Members added on
main since the original proposal were assigned to their topic shard.
Fixes#79963
Part of #78647
Co-authored-by: Finn763 <148605355+Finn763@users.noreply.github.com>
Main now derives $focusedStoredSessionId from $focusedTreePaneId, which keeps
the last chat while the sessions list holds focus. Drop this branch's second
derivation and widen that rule instead: any non-chat zone (Files, Terminal,
preview tabs) keeps the remembered chat, so the Files rail no longer jumps to
the primary's project when you click into it.
$focusedStoredSessionId now derives from $activeTreeGroup + $layoutTree, so
focusing a tile in tests must set the main zone's active pane, not only the
session-focus pane atom.
When switching between multiple open project tabs, the primary workspace
could temporarily report an empty cwd before workspaceCwdOwner caught up.
Fall back to the confirmed session cwd from $sessions / $focusedSessionState
so the right sidebar immediately resolves the workspace directory.
- Bind $repoChangeByPath to $repoStatusByCwd so file status decorations
persist independently of active session ownership or detached chats.
- Add directory-level untracked inheritance to repoChangeKindForPath.
- Register right-sidebar workspace cwd in registerRepoStatusCwd and trigger
refreshRepoStatus on sidebar tree refresh.
When switching between session tabs/tiles in the desktop app, the right sidebar file tree retained the primary workspace cwd ($selectedStoredSessionId) instead of updating to the focused session tile's cwd.
Bind RightSidebarPane to $focusedStoredSessionId and $focusedSessionState so the file tree updates immediately upon tab/tile interaction.
Tray.destroy() leaves the StatusNotifierItem interface exported, so a later
new Tray() in the same process cannot re-export and the panel icon dies.
Park the instance until quit or host loss instead of destroying it on disable.
Address review feedback from @kvnloo:
1. The client-direct exact-phrase hallucination check stripped '.'/'!'
anywhere in the transcript, while the relay only strips trailing
('cleaned.rstrip(".!")' in tools/voice_mode_transcript.py). 'thank. you'
was dropped client-direct but kept relayed. Now a trailing strip
(/[.!]+$/), with a regression pinning internal punctuation as a real
turn on both paths.
2. Turning voice.barge_in off after the monitor armed did not disarm it:
ensureBargeMonitor() gates on $bargeInEnabled only at creation, so a
live config refresh saying barge-in is off left the armed monitor
interrupting on speech. An atom subscription now stops + clears the
live monitor on true->false (a mid-flight capture is dropped and the
loop resumes normal listening). Focused mid-turn toggle test added.
tsc -p found request.id used as string after a truthiness guard; narrow to
typeof string and capture before the async closure. Cast the script-runner
stub in the test to the runner type.
After pop-out the live webview lives in the Browser window while the chat
window still owns the active-session gate, so drive_preview/read_preview
answered NOTHING_OPEN/empty from the chat window. Forward both over the
shared window relay (IPC in the app, BroadcastChannel as a dev fallback)
and answer with the pop-out's result; a silent pop-out falls back to the
legacy local error. Both pop-out legs now ride one relay surface.
Adapted from #101530 by FalconOrtiz (rebased onto the server-requests
split; transport moved from BroadcastChannel-only to the IPC relay so it
works in packaged file:// windows).
Co-authored-by: FalconOrtiz <falcon.ortiz11@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Fluid Voice (and dictation tools like it) pauses other audio over the
macOS media session to hear the microphone, then inserts the transcript
into the focused field — a guarded paste in Chromium/Electron. No
media-session resume signal ever follows, which is why preserving the
stream alone (the reporter's own experiment) still left radio paused.
Track the generation that last reached 'playing' (playGeneration); an
external pause keeps the element intact (status 'paused'), and a paste
into an editable field resumes it via a plain element.play(). The
plugin's own Pause stays destructive (toggle() from 'live' runs stop(),
clearing the element), so a paste never resumes an intentionally paused
stream and Play reconnects from scratch. Never resumed on window focus.
Credit: Kokhlo's PR #108115 contributed the non-destructive external
pause and the survival test.
Co-authored-by: kokhlo <konstantin.khlopkov93@gmail.com>
Browser/media controls pause the element outside our buttons. The old handler
tore down the stream (cleared src, invoked load(), removed the element, and
advanced the generation) before macOS Fluid Voice or any external media
controller could resume it.
The internal Pause control already increments the generation first, so its
resulting media pause is safely ignored by current(). The external-event
branch can therefore be non-destructive: set status to 'paused' instead of
calling stop(). The 'playing' event already transitions the state back to
'live', so resume is possible.
Add a focused lifecycle test: external pause changes status to 'paused',
element and src survive, and a synthetic 'playing' event brings the stream
back to 'live'.
Skills mounted from skills.external_dirs are externally authored, but every
provenance surface flattened them into 'agent': the usage report, the
dashboard's set-based twin, the desktop catalog/detail UI, and the learning
journey graph (where a used external mount rendered as a learned skill).
- tools/skill_usage: provenance() gains an 'external' tier (external-only:
a local copy of the same name keeps winning, mirroring is_agent_created's
external exclusion and _external_skill_names() scans external dirs once
for the router's set-based twin.
- web_routers/skills: /api/skills classifies hub > bundled > external > agent.
- learning_graph: skills whose path resolves under an external dir are tagged
source='external' and are never learning milestones — a mount is configured,
not learned, even when used.
- desktop: SkillInfo.provenance gains 'external'; the catalog labels mounts
'external' and the detail pane keeps them editable in place. Origin only,
never mutability: read-only enforcement for external mounts was deliberately
rejected by maintainers (commit 8c8fc6c1ec / PR #17512 — a refusal just
breeds silent local duplicates). Provenance decisions extracted into
skill-provenance.ts pure helpers (testable without source-reading tests).
- curator: usage report/filter knows the 'external' origin.
The desktop edit logic in this commit is the natural reading of 8c8fc6c1ec
(external dirs exist so the user can point Hermes at a vault they curate
elsewhere); if maintainers prefer the read-only half as well, it slots in
behind isEditableProvenance() in one place.
EOF
)
Part of #79958
Splits the Traditional Chinese override table (4419 lines on main) into the
same 11 topic files as the source PR, leaving zh-hant.ts as the composing
defineLocale file (177 lines) with the export name zhHant unchanged:
- zh-hant_artifacts.ts (artifacts, artifactCard, artifactPreview, preview)
- zh-hant_assistant.ts (assistant)
- zh-hant_boot.ts (boot, remoteDisplayBanner, updates, guidedGreeting, install, onboarding)
- zh-hant_capabilities.ts (skills, starmap, agents)
- zh-hant_chat.ts (composer, statusStack, prompts, desktop, tips)
- zh-hant_chrome.ts (fileMenu, titlebar, sidebar, shell, rightSidebar, zones, contextMenu)
- zh-hant_command_center.ts (commandCenter, messaging, profiles, cron)
- zh-hant_common.ts (common, billingBlock, ui)
- zh-hant_connectors.ts (sessionImport)
- zh-hant_diagnostics.ts (notifications, sendDiagnostics, errors)
- zh-hant_settings.ts (language, settings, modelPicker, modelVisibility)
Rebuilt against current main: main added six top-level keys since the source
PR branched (externalOpenFailed, sharedMetrics, intro, catalog, modelAssignment,
interfaceMode). They sit outside every topic boundary of the split scheme, so
they stay inline in the composing file at their original positions; the 41
mapped keys moved to the same topics with the same filenames as the source PR.
zh-hant is a sparse override: siblings carry satisfies Pick<TranslationOverrides,...>
and the composition keeps the original top-level key order. No re-export shim.
Co-authored-by: finn763 <165816600+finn763@users.noreply.github.com>
Unstacked editor sat in a 1fr grid column with flex-1 + an 8rem min-width floor, so the contenteditable collapsed to the measured 129pt sliver while queue controls crowded the row. Fill the grid track with w-full min-w-0 instead.
stripGeneratedImageEchoes stripped EVERY markdown image and #media: link
from a final message whenever an image_generate tool result was present
in the same turn's parts. A completed response that carried a MEDIA:
directive for a different file lost its attachment link entirely, and a
message whose only content was such a directive rendered blank after the
empty text part was dropped by dedupeGeneratedImageEchoesInParts
(#80621).
Scope the strip to echoes of the actual generated source:
- markdown images are removed only when the src matches a source,
with a lookahead so a longer path (a.png.bak) is not eaten by a
prefix source (a.png)
- #media: links are removed only when the encoded path matches a
source, anchored to the closing paren so prefix paths survive
- bare-path stripping no longer matches inside markdown link
destinations, so [label](/tmp/a.png) is not corrupted to [label]()
Also wrap parseMarkdownIntoBlocksCached in a lexer-throw fallback so a
single pathological message cannot unwind through MessageRenderBoundary
to the root boundary and blank the whole transcript.
Closes#80621
Rebased onto current main: the strip keeps main's whitespace semantics — main deliberately stopped normalizing whole-document whitespace through media extraction (3e1b7af21b), so the same-file echo test now expects the two-space gap the removed image leaves instead of the old collapsed form. The scoped strip itself is unchanged.
React 19.1+ stubs `react`.act to undefined in the production build.
@testing-library/react@16's act() delegates to `React.act`, so any run
with NODE_ENV=production throws "React.act is not a function" and every
render() in the ui (jsdom) project fails — ~957 tests across the whole
src tree.
Force NODE_ENV=development for the ui test worker via the project's
test.env so `act` resolves to a real function regardless of any ambient
NODE_ENV (e.g. a shell that inherited NODE_ENV=production from a desktop
launch). This restores the ui project without touching the app's React
runtime or the production build.
Verified: src/store (597), src/lib (604), src/components (545), src/app
(1374), src/i18n + misc (28) all pass. Remaining ui failures are two
pre-existing test files (no-native-title.test.ts, ipc.test.ts) that
import node: builtins under jsdom — unrelated to this change.
Vercel's WAF denies some residential IP ranges for the whole docs domain,
which leaves the Bot Mode Skills Hub picker on a 403 block page with no
alternate origin (#118203). Probe the primary picker URL before each
browse session and fall back to the equivalent GitHub Pages deployment
when it is unreachable, refuses us, or hangs past an 8s deadline; picker
installs are accepted from either trusted origin but still gated on our
own frame's contentWindow. Documented as an FAQ troubleshooting entry.
Re-lands the surviving half of #118206 (its Capabilities picker target
was since deleted by the native catalog).
Fixes#118203
Co-authored-by: KoNit-K <konit.block@protonmail.com>
Closes#101263
ContextMenuItem onSelect handlers (Copy path, Delete…, Reveal…) fire
their intended actions AND open the file preview — every menu item except
Rename… triggers onPreviewFile alongside its own action.
Root cause: Radix ContextMenu renders its portal content in document.body,
so clicks on menu items bubble as React synthetic fiber events (not DOM
events) to the nearest fiber ancestor — the row container div. The
container's onClick=node.handleClick then fires onActivate →
onPreviewFile → openPreview for every menu item click.
A stopPropagation() wrapper before node.handleClick(e) does NOT stop
this: React dispatches the portal click directly onto the portal branch
of the fiber tree (there is no shared DOM bubble path through the row),
so the container's onClick still runs for it and the wrapper then calls
handleClick itself.
The Rename… flow already has a guard ($renamingPath) because it mounts an
inline input that would be immediately blurred by a preview panel opening.
The same guard cannot be applied broadly because single-click and
double-click legitimately need to activate previews.
Fix: only run arborist's handleClick when the click's DOM target is
actually inside the row container (e.currentTarget.contains(e.target)).
Portal-originated clicks — whose target lives in document.body, outside
the row — fail the containment check and are ignored. Real row clicks
(single, double, shift-attach) land inside the row's DOM and pass it.
review/file-tree.tsx is NOT affected: its menu wraps the row div as a
plain parent (no portal→fiber re-entry through the row), so it is left
untouched.
Regression test (tree.test.tsx) renders the real ProjectTree with the
real FileEntryContextMenu under jsdom: clicking "Copy path" in the
portaled menu must not call onPreviewFile (fails on main), while a real
single click still selects the row and a real double click still
previews the file (positive controls).
Cache mounted prompt membership and binary-search current outer group geometry.
Invalidate membership only for message DOM changes; keep geometry live for
resizes and preserve content-visibility by never measuring skipped descendants.
Refs NousResearch/hermes-agent#125766
Refs NousResearch/hermes-agent#118782
A barge-in capture that survives the 5 s interrupt-settle window with
live busy still true could never be submitted: the composer's
submitVoiceTurn refuses while busy. Fold #123371's onSubmitRef pattern
(submitCapturedUtterance reads the latest callback, mirroring
onInterruptRef) and, when busy still never clears, park the transcript
in the composer input and focus it so a spoken interruption is always
visible — never silently dropped (#123357 part 1). Also fixes the
composer-voice test mocks for current main (scope/focus exports,
nanostores computed).
Co-authored-by: Yuan Li <dskwelmcy@163.com>