workspacePrompt.test.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // @vitest-environment node
  2. import { describe, expect, it } from 'vitest'
  3. import { buildWorkspacePrompt, workspaceEnvironmentPrompt, type WorkspacePromptEnvironment, type WorkspacePromptInput } from '../../src/server/workspacePrompt'
  4. function environment(): WorkspacePromptEnvironment {
  5. return {
  6. open: { computer: true, server: true, vm: true, cloud: true },
  7. tools: { desktopView: true, desktopFile: true, vm: true, cloud: true, plugins: false },
  8. deviceContext: '本轮用户消息来自电脑「工作 Mac」。「本机」指这台电脑。',
  9. onlineHosts: '工作 Mac(host=mac-id)', cwd: '/srv/hermes/project', fileTransferMaxMiB: 50,
  10. }
  11. }
  12. function input(): WorkspacePromptInput {
  13. return {
  14. agent: { id: 'bot', name: '助手', avatar: '', instructions: '规则原文\n保留格式', description: '处理开发任务', job: '开发与验证', voice: 'concise', actBias: 'act_now', nodeId: 'local', profile: 'dev', archived: false, revision: 7, createdAt: 1, updatedAt: 1 },
  15. conversation: { id: 'chat', kind: 'direct', name: '对话', avatar: '', memberIds: ['bot'], administratorId: 'bot', mode: 'free', instructions: '', autoReplyIds: [], maxReplyRounds: 1, archived: false, pinned: false, readSeq: 0, lastSeq: 0, preview: '', createdAt: 1, updatedAt: 1 },
  16. members: [{ id: 'bot', name: '助手' }],
  17. work: { depth: 0, requiredReply: true, replyMode: 'mentioned', messageId: 'message' },
  18. run: { mentionIds: [] }, environment: environment(),
  19. memory: '已确认的项目事实', noReply: '[[YAOYAO_NO_REPLY_V1]]', marker: '[yaoyao-run:run:result]',
  20. content: '读取本机文件\n然后核对结果', contentKind: 'user', attachmentRefs: ['[附件](ref:file)'],
  21. }
  22. }
  23. describe('Bot environment routing', () => {
  24. it('describes all mounted environments conditionally without moving the model or the message origin', () => {
  25. const text = workspaceEnvironmentPrompt(environment())
  26. expect(text).toContain('模型会话运行在服务端 Hermes')
  27. expect(text).toContain('「本机」只指本轮消息来源电脑')
  28. expect(text).toContain('不得切换其他机器代做')
  29. expect(text).toContain('host="server"')
  30. expect(text).toContain('desktop_shell')
  31. expect(text).toContain('computer_shell')
  32. expect(text).toContain('cloud_computer_*')
  33. expect(text).toContain('最大 50 MiB')
  34. expect(text).not.toContain('25 MiB')
  35. expect(text).not.toContain('当前环境是')
  36. expect(text).not.toContain('一律用 desktop_*')
  37. expect(text.match(/工作 Mac/g)).toHaveLength(2) // source and inventory, not one list per environment
  38. expect(text).toContain('该路径仅属于 Hermes 运行环境')
  39. })
  40. it('distinguishes global policy from mounted capabilities and does not promise native tools', () => {
  41. const env = environment()
  42. env.tools = { desktopView: false, desktopFile: false, vm: false, cloud: false, plugins: false }
  43. const text = workspaceEnvironmentPrompt(env)
  44. expect(text).toContain('开放不等于在线或已授权')
  45. expect(text).toContain('以本轮实际工具目录为准')
  46. expect(text).not.toContain('工具均可使用')
  47. for (const tool of ['desktop_file_copy', 'desktop_environment_view', 'computer_shell', 'cloud_computer_*']) expect(text).not.toContain(tool)
  48. })
  49. it('does not require a screenshot for file-only computers', () => {
  50. const env = environment()
  51. env.tools.desktopView = false
  52. const text = workspaceEnvironmentPrompt(env)
  53. expect(text).toContain('文件与命令操作无需先截图')
  54. expect(text).not.toContain('desktop_environment_view')
  55. expect(text).toContain('desktop_file_copy')
  56. env.tools.desktopFile = false
  57. env.tools.desktopView = true
  58. const view = workspaceEnvironmentPrompt(env)
  59. expect(view).toContain('desktop_environment_view')
  60. expect(view).not.toContain('电脑文件与命令工具已挂载')
  61. expect(view).not.toContain('desktop_file_copy')
  62. })
  63. it('cannot turn Grok model discussion, old VM text or a follow-up into environment selection', () => {
  64. const fixture = input()
  65. fixture.environment.tools.cloud = false
  66. fixture.environment.tools.vm = false
  67. const prompts = ['介绍 Grok 模型', '以前使用过虚拟机', '继续'].map(content => buildWorkspacePrompt({ ...fixture, content }))
  68. const environments = prompts.map(text => text.split('【本轮环境与设备】')[1]!.split('【Bot 记忆与相关事实】')[0])
  69. expect(new Set(environments).size).toBe(1)
  70. expect(environments[0]).not.toContain('cloud_computer_*')
  71. expect(environments[0]).not.toContain('computer_shell')
  72. })
  73. })
  74. describe('Bot prompt composition', () => {
  75. it('keeps persona, provenance, user content and attachments in their own sections', () => {
  76. const fixture = input(), text = buildWorkspacePrompt(fixture)
  77. expect(text).toContain(`版本 7):\n${fixture.agent.instructions}`)
  78. expect(text).toContain('你是 助手。')
  79. expect(text).toContain('直接行动:合理默认即可开工')
  80. expect(text).toContain(`【本轮用户消息】\n${fixture.content}`)
  81. expect(text).toContain('【本轮附件】\n[附件](ref:file)')
  82. expect(text).toContain(fixture.marker)
  83. expect(text.indexOf('【Bot 身份与长期规则】')).toBeLessThan(text.indexOf('【本轮任务与协作】'))
  84. expect(text.indexOf('【本轮环境与设备】')).toBeLessThan(text.indexOf('【Bot 记忆与相关事实】'))
  85. expect(text.indexOf('【Bot 记忆与相关事实】')).toBeLessThan(text.indexOf('【本轮用户消息】'))
  86. })
  87. it('preserves discussion participation, member roles and history boundaries', () => {
  88. const fixture = input()
  89. fixture.conversation = { ...fixture.conversation, kind: 'group', memberRoles: { bot: { name: '审核', description: '核实事实' } } }
  90. fixture.run = { mentionIds: ['bot'], discussion: { memberIds: ['bot'], rounds: 2 }, projectId: 'project' }
  91. fixture.work.depth = 1
  92. fixture.contentKind = 'history'
  93. const text = buildWorkspacePrompt(fixture)
  94. expect(text).toContain('平等讨论,第 2 轮')
  95. expect(text).toContain('@助手:审核;核实事实')
  96. expect(text).toContain('本轮用户指定成员:@助手')
  97. expect(text).toContain('不要用私信或 @ 再次派发本轮工作')
  98. expect(text).toContain('【本轮对话上下文】')
  99. expect(text).toContain('当前项目 ID:project')
  100. })
  101. it('preserves automatic silence and does not offer unmounted export tools to a helper', () => {
  102. const fixture = input()
  103. fixture.work.requiredReply = false
  104. fixture.work.replyMode = 'automatic'
  105. fixture.agent.temporaryGoalId = 'goal'
  106. fixture.environment.tools.vm = false
  107. const text = buildWorkspacePrompt(fixture)
  108. expect(text).toContain('完整答复只能是 [[YAOYAO_NO_REPLY_V1]]')
  109. expect(text).toContain('不要创建团队或改变自身权限')
  110. expect(text).not.toContain('computer_export')
  111. })
  112. })