Files
hermes-agent/apps/desktop/electron/composer-paste.ts
teknium1 60dd9778c6 fix(desktop): large text pastes attach from HERMES_HOME/composer-pastes when the chat cwd is elsewhere
Desktop persisted a large paste under Electron's userData dir and attached
it as `@file:<abs path>`; the tui_gateway prompt path expands that reference
with `allowed_root=cwd`, so `_resolve_path` refused it with "path is outside
the allowed workspace" whenever the chat's cwd was not an ancestor of the
paste dir (always on Linux/macOS, and on Windows for any project cwd).

Electron now writes pastes under `<HERMES_HOME>/composer-pastes`, and
`agent/context_references.py::_resolve_path` admits exactly that anchored
directory (active profile home and global root, via file_safety._hermes_dirs)
as the one root besides `allowed_root`. A sibling directory that merely
contains the substring stays refused; the credential deny-list still runs
on the admitted path.

Supersedes the substring whitelist proposed in #117150.

Fixes #117149
2026-09-20 19:45:30 -07:00

28 lines
1.2 KiB
TypeScript

import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
export const COMPOSER_PASTES_DIRNAME = 'composer-pastes'
/**
* Persist a large plain-text paste as a `.txt` file the composer can attach
* as a chip instead of flooding the input. The renderer never chooses the
* path: the file lands in a Desktop-managed directory with a generated name,
* mirroring how `writeComposerImage` handles pasted images.
*
* `hermesHome` must be the HERMES_HOME root, not Electron's userData dir: the
* backend admits `@file:` attachments outside the chat's cwd only from
* `<HERMES_HOME>/composer-pastes` (agent/context_references.py::_resolve_path),
* and on Linux/macOS userData is a different tree (#117149).
*/
export async function writeComposerPaste(hermesHome: string, text: string): Promise<string> {
const dir = path.join(hermesHome, COMPOSER_PASTES_DIRNAME)
await fs.promises.mkdir(dir, { recursive: true })
const stamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', '')
const random = crypto.randomBytes(3).toString('hex')
const filePath = path.join(dir, `pasted_content_${stamp}_${random}.txt`)
await fs.promises.writeFile(filePath, text, 'utf8')
return filePath
}