sessionWorkingDirectory.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344
  1. import { HttpError } from './errors.js'
  2. import type { UpstreamRequestOptions, UpstreamResponse } from './upstream.js'
  3. type Request = (path: string, options: UpstreamRequestOptions) => Promise<UpstreamResponse>
  4. export interface AppliedWorkingDirectory { configured: string; resolved: string }
  5. /** Read the selected Hermes profile, never resolve a remote path on the Web host. */
  6. export async function configuredWorkingDirectory(request: Request, profile: string): Promise<string | undefined> {
  7. const response = await request('/api/config', {
  8. search: new URLSearchParams({ profile }), cache: 'reload',
  9. })
  10. if (response.status !== 200) {
  11. throw new HttpError(502, '无法读取机器人的 terminal.cwd,未开始执行', 'working_directory_unavailable')
  12. }
  13. let config: any
  14. try { config = JSON.parse(response.body.toString()) } catch {
  15. throw new HttpError(502, '机器人工作目录配置响应无效', 'invalid_working_directory')
  16. }
  17. const raw = config?.terminal?.cwd
  18. // Older Hermes installations may not expose this setting.
  19. if (raw === undefined || raw === null || raw === '') return undefined
  20. if (typeof raw !== 'string' || !raw.trim() || raw.length > 4096 || /[\u0000-\u001f\u007f]/.test(raw)) {
  21. throw new HttpError(502, '机器人的 terminal.cwd 配置无效', 'invalid_working_directory')
  22. }
  23. return raw.trim()
  24. }
  25. /** Session-only RPC: updates tool execution and persisted cwd without changing global config. */
  26. export async function applyWorkingDirectory(
  27. rpc: (method: string, params: Record<string, unknown>) => Promise<any>,
  28. sessionID: string,
  29. configured: string | undefined,
  30. previous?: AppliedWorkingDirectory,
  31. observedCwd?: string,
  32. ): Promise<AppliedWorkingDirectory | undefined> {
  33. if (!configured) return undefined
  34. if (previous?.configured === configured) return previous
  35. if (observedCwd === configured) return { configured, resolved: observedCwd }
  36. const info = await rpc('session.cwd.set', { session_id: sessionID, cwd: configured })
  37. if (typeof info?.cwd !== 'string' || !info.cwd.trim()) {
  38. throw new HttpError(502, 'Hermes 未确认会话工作目录,未开始执行', 'working_directory_unconfirmed')
  39. }
  40. return { configured, resolved: info.cwd }
  41. }